| author | Brian Hackett <bhackett1024@gmail.com> |
| Sat, 03 Dec 2011 10:34:26 -0800 | |
| changeset 81351 | 13afcd4c097cf52b3fb653d9c59ee07bd78d863e |
| parent 81350 | 3dca8a1ee5febc58c7297986b6f1d219fc5a3321 (current diff) |
| parent 81233 | a68c96c1d8e0a64b9008d95ac52e381d88751521 (diff) |
| child 81355 | c2102c45c8da7870239f2b313359a6da18703a4a |
| push id | 21565 |
| push user | bhackett@mozilla.com |
| push date | Sat, 03 Dec 2011 20:25:52 +0000 |
| treeherder | mozilla-central@13afcd4c097c [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| milestone | 11.0a1 |
| first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
| last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/src/base/nsRootAccessible.cpp +++ b/accessible/src/base/nsRootAccessible.cpp @@ -391,30 +391,31 @@ nsRootAccessible::ProcessDOMEvent(nsIDOM nsINode* targetNode = accessible->GetNode(); #ifdef MOZ_XUL nsRefPtr<nsXULTreeAccessible> treeAcc; if (targetNode->IsElement() && targetNode->AsElement()->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) { treeAcc = do_QueryObject(accessible); - - if (eventType.EqualsLiteral("TreeViewChanged")) { - treeAcc->TreeViewChanged(); - return; - } + if (treeAcc) { + if (eventType.EqualsLiteral("TreeViewChanged")) { + treeAcc->TreeViewChanged(); + return; + } - if (eventType.EqualsLiteral("TreeRowCountChanged")) { - HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); - return; - } + if (eventType.EqualsLiteral("TreeRowCountChanged")) { + HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); + return; + } - if (eventType.EqualsLiteral("TreeInvalidated")) { - HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); - return; + if (eventType.EqualsLiteral("TreeInvalidated")) { + HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); + return; + } } } #endif if (eventType.EqualsLiteral("RadioStateChange")) { PRUint64 state = accessible->State(); // radiogroup in prefWindow is exposed as a list,
--- a/accessible/tests/mochitest/treeupdate/test_textleaf.html +++ b/accessible/tests/mochitest/treeupdate/test_textleaf.html @@ -91,33 +91,33 @@ this.eventSeq = [ new invokerChecker(EVENT_REORDER, this.containerNode) ]; this.invoke = function removeTextData_invoke() { var tree = { - role: ROLE_SECTION, + role: ROLE_PARAGRAPH, children: [ { role: ROLE_TEXT_LEAF, name: "text" } ] }; testAccessibleTree(this.containerNode, tree); this.textNode.data = ""; } this.finalCheck = function removeTextData_finalCheck() { var tree = { - role: ROLE_SECTION, + role: ROLE_PARAGRAPH, children: [] }; testAccessibleTree(this.containerNode, tree); } this.getID = function removeTextData_finalCheck() { return "remove text data of text node inside '" + aID + "'."; @@ -142,17 +142,18 @@ // remove onclick attribute, text leaf shouldn't have any action gQueue.push(new removeOnClickAttr("div")); // set onclick attribute making span accessible, it's inserted into tree // and adopts text leaf accessible, text leaf should have an action gQueue.push(new setOnClickNRoleAttrs("span")); // text data removal of text node should remove its text accessible - gQueue.push(new removeTextData("container2")); + gQueue.push(new removeTextData("p")); + gQueue.push(new removeTextData("pre")); gQueue.invoke(); // SimpleTest.finish() will be called in the end } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTest); </script> </head> @@ -163,24 +164,30 @@ href="https://bugzilla.mozilla.org/show_bug.cgi?id=545465"> Mozilla Bug 545465 </a> <a target="_blank" title="Make sure accessible tree is correct when rendered text is changed" href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652"> Mozilla Bug 625652 </a> + <a target="_blank" + title="Remove text accesible getting no text inside a preformatted area" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=706335"> + Mozilla Bug 706335 + </a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <div id="container"> <div id="div">div</div> <span id="span">span</span> </div> - <div id="container2">text</div> + <p id="p">text</p> + <pre id="pre">text</pre> <div id="eventdump"></div> </body> </html>
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -198,17 +198,17 @@ pref("app.update.incompatible.mode", 0); // Symmetric (can be overridden by individual extensions) update preferences. // e.g. // extensions.{GUID}.update.enabled // extensions.{GUID}.update.url // .. etc .. // pref("extensions.update.enabled", true); -pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%¤tAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%"); +pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%¤tAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%"); pref("extensions.update.interval", 86400); // Check for updates to Extensions and // Themes every day // Non-symmetric (not shared by extensions) extension-specific [update] preferences pref("extensions.getMoreThemesURL", "https://addons.mozilla.org/%LOCALE%/firefox/getpersonas"); pref("extensions.dss.enabled", false); // Dynamic Skin Switching pref("extensions.dss.switchPending", false); // Non-dynamic switch pending after next // restart.
--- a/browser/base/content/browser-tabview.js +++ b/browser/base/content/browser-tabview.js @@ -203,16 +203,17 @@ let TabView = { // ___ find the deck this._deck = document.getElementById("tab-view-deck"); // ___ create the frame this._iframe = document.createElement("iframe"); this._iframe.id = "tab-view"; this._iframe.setAttribute("transparent", "true"); + this._iframe.setAttribute("tooltip", "tab-view-tooltip"); this._iframe.flex = 1; let self = this; window.addEventListener("tabviewframeinitialized", function onInit() { window.removeEventListener("tabviewframeinitialized", onInit, false); self._isFrameLoading = false; @@ -230,16 +231,22 @@ let TabView = { self._tabCloseEventListener = null; } self._initFrameCallbacks.forEach(function (cb) cb()); self._initFrameCallbacks = []; }, false); this._iframe.setAttribute("src", "chrome://browser/content/tabview.html"); this._deck.appendChild(this._iframe); + + // ___ create tooltip + let tooltip = document.createElement("tooltip"); + tooltip.id = "tab-view-tooltip"; + tooltip.setAttribute("onpopupshowing", "return TabView.fillInTooltip(document.tooltipNode);"); + document.getElementById("mainPopupSet").appendChild(tooltip); }, // ---------- getContentWindow: function TabView_getContentWindow() { return this._window; }, // ---------- @@ -348,18 +355,20 @@ let TabView = { event.preventDefault(); self._initFrame(function() { let groupItems = self._window.GroupItems; let tabItem = groupItems.getNextGroupItemTab(event.shiftKey); if (!tabItem) return; - // Switch to the new tab - window.gBrowser.selectedTab = tabItem.tab; + if (gBrowser.selectedTab.pinned) + groupItems.updateActiveGroupItemAndTabBar(tabItem, {dontSetActiveTabInGroup: true}); + else + gBrowser.selectedTab = tabItem.tab; }); } }, true); }, // ---------- // Prepares the tab view for undo close tab. prepareUndoCloseTab: function TabView_prepareUndoCloseTab(blankTabToRemove) { @@ -435,10 +444,34 @@ let TabView = { // enable session restore if necessary if (Services.prefs.getIntPref(this.PREF_STARTUP_PAGE) != 3) { Services.prefs.setIntPref(this.PREF_STARTUP_PAGE, 3); // show banner this._window.UI.notifySessionRestoreEnabled(); } + }, + + // ---------- + // Function: fillInTooltip + // Fills in the tooltip text. + fillInTooltip: function fillInTooltip(tipElement) { + let retVal = false; + let titleText = null; + let direction = tipElement.ownerDocument.dir; + + while (!titleText && tipElement) { + if (tipElement.nodeType == Node.ELEMENT_NODE) + titleText = tipElement.getAttribute("title"); + tipElement = tipElement.parentNode; + } + let tipNode = document.getElementById("tab-view-tooltip"); + tipNode.style.direction = direction; + + if (titleText) { + tipNode.setAttribute("label", titleText); + retVal = true; + } + + return retVal; } };
--- a/browser/base/content/pageinfo/pageInfo.js +++ b/browser/base/content/pageinfo/pageInfo.js @@ -987,17 +987,17 @@ function makePreview(row) newImage.controls = true; isAudio = true; document.getElementById("theimagecontainer").collapsed = false; document.getElementById("brokenimagecontainer").collapsed = true; } #endif else { - // fallback image for protocols not allowed (e.g., data: or javascript:) + // fallback image for protocols not allowed (e.g., javascript:) // or elements not [yet] handled (e.g., object, embed). document.getElementById("brokenimagecontainer").collapsed = false; document.getElementById("theimagecontainer").collapsed = true; } var imageSize = ""; if (url && !isAudio) { if (width != physWidth || height != physHeight) { @@ -1223,13 +1223,11 @@ function selectImage() return; } } } function checkProtocol(img) { var url = img[COL_IMAGE_ADDRESS]; - if (/^data:/.test(url) && /^image\//.test(img[COL_IMAGE_NODE].type)) - return true; - const regex = /^(https?|ftp|file|about|chrome|resource):/; - return regex.test(url); + return /^data:image\//i.test(url) || + /^(https?|ftp|file|about|chrome|resource):/.test(url); }
--- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -43,18 +43,16 @@ relativesrcdir = browser/base/content/t include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/rules.mk _TEST_FILES = \ test_feed_discovery.html \ feed_discovery.html \ test_bug395533.html \ bug395533-data.txt \ - test_contextmenu.html \ - subtst_contextmenu.html \ ctxmenu-image.png \ video.ogg \ test_offlineNotification.html \ offlineChild.html \ offlineChild.cacheManifest \ offlineChild.cacheManifest^headers^ \ offlineChild2.html \ offlineChild2.cacheManifest \ @@ -67,16 +65,24 @@ include $(topsrcdir)/config/rules.mk bug364677-data.xml^headers^ \ test_offline_gzip.html \ gZipOfflineChild.html \ gZipOfflineChild.html^headers^ \ gZipOfflineChild.cacheManifest \ gZipOfflineChild.cacheManifest^headers^ \ $(NULL) +# test_contextmenu.html is disabled on Linux due to bug 513558 +ifneq (gtk2,$(MOZ_WIDGET_TOOLKIT)) +_TEST_FILES += \ + test_contextmenu.html \ + subtst_contextmenu.html \ + $(NULL) +endif + # The following tests are disabled because they are unreliable: # browser_bug423833.js is bug 428712 # browser_sanitize-download-history.js is bug 432425 # # browser_sanitizeDialog_treeView.js is disabled until the tree view is added # back to the clear recent history dialog (santize.xul), if it ever is (bug # 480169)
--- a/browser/base/content/test/browser_customize.js +++ b/browser/base/content/test/browser_customize.js @@ -1,11 +1,12 @@ function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); var frame = document.getElementById("customizeToolbarSheetIFrame"); frame.addEventListener("load", testCustomizeFrameLoadedPre, true); document.getElementById("cmd_CustomizeToolbars").doCommand(); } function testCustomizeFrameLoadedPre(){ // This load listener can be called before
--- a/browser/components/build/nsBrowserCompsCID.h +++ b/browser/components/build/nsBrowserCompsCID.h @@ -46,19 +46,16 @@ #ifdef XP_MACOSX #define NS_SAFARIPROFILEMIGRATOR_CID \ { 0x29e3b139, 0xad19, 0x44f3, { 0xb2, 0xc2, 0xe9, 0xf1, 0x3b, 0xa2, 0xbb, 0xc6 } } #endif #define NS_OPERAPROFILEMIGRATOR_CID \ { 0xf34ff792, 0x722e, 0x4490, { 0xb1, 0x95, 0x47, 0xd2, 0x42, 0xed, 0xca, 0x1c } } -#define NS_SEAMONKEYPROFILEMIGRATOR_CID \ -{ 0x9a28ffa7, 0xe6ef, 0x4b52, { 0xa1, 0x27, 0x6a, 0xd9, 0x51, 0xde, 0x8e, 0x9b } } - #define NS_SHELLSERVICE_CID \ { 0x63c7b9f4, 0xcc8, 0x43f8, { 0xb6, 0x66, 0xa, 0x66, 0x16, 0x55, 0xcb, 0x73 } } #define NS_SHELLSERVICE_CONTRACTID \ "@mozilla.org/browser/shell-service;1" #define NS_RDF_FORWARDPROXY_INFER_DATASOURCE_CID \ { 0x7a024bcf, 0xedd5, 0x4d9a, { 0x86, 0x14, 0xd4, 0x4b, 0xe1, 0xda, 0xda, 0xd3 } }
--- a/browser/components/migration/content/migration.js +++ b/browser/components/migration/content/migration.js @@ -181,18 +181,18 @@ var MigrationWizard = { if (this._autoMigrate) this._wiz.currentPage.next = "homePageImport"; else if (this._bookmarks) this._wiz.currentPage.next = "migrating" else this._wiz.currentPage.next = "importItems"; var sourceProfiles = this._migrator.sourceProfiles; - if (sourceProfiles && sourceProfiles.Count() == 1) { - var profileName = sourceProfiles.QueryElementAt(0, Components.interfaces.nsISupportsString); + if (sourceProfiles && sourceProfiles.length == 1) { + var profileName = sourceProfiles.queryElementAt(0, Ci.nsISupportsString); this._selectedProfile = profileName.data; } else this._selectedProfile = ""; } }, // 2 - [Profile Selection] @@ -206,20 +206,19 @@ var MigrationWizard = { var profiles = document.getElementById("profiles"); while (profiles.hasChildNodes()) profiles.removeChild(profiles.firstChild); // Note that this block is still reached even if the user chose 'From File' // and we canceled the dialog. When that happens, _migrator will be null. if (this._migrator) { var sourceProfiles = this._migrator.sourceProfiles; - var count = sourceProfiles.Count(); - for (var i = 0; i < count; ++i) { + for (var i = 0; i < sourceProfiles.length; ++i) { var item = document.createElement("radio"); - var str = sourceProfiles.QueryElementAt(i, Components.interfaces.nsISupportsString); + var str = sourceProfiles.queryElementAt(i, Ci.nsISupportsString); item.id = str.data; item.setAttribute("label", str.data); profiles.appendChild(item); } } profiles.selectedItem = this._selectedProfile ? document.getElementById(this._selectedProfile) : profiles.firstChild; },
--- a/browser/components/migration/public/nsIBrowserProfileMigrator.idl +++ b/browser/components/migration/public/nsIBrowserProfileMigrator.idl @@ -31,20 +31,20 @@ * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" -interface nsISupportsArray; +interface nsIArray; interface nsIProfileStartup; -[scriptable, uuid(f8365b4a-da55-4e47-be7a-230142360f62)] +[scriptable, uuid(5f445759-86a8-4dd3-ab84-4fc5341d9d9d)] interface nsIBrowserProfileMigrator : nsISupports { /** * profile items to migrate. use with migrate(). */ const unsigned short ALL = 0x0000; const unsigned short SETTINGS = 0x0001; const unsigned short COOKIES = 0x0002; @@ -63,17 +63,18 @@ interface nsIBrowserProfileMigrator : ns void migrate(in unsigned short aItems, in nsIProfileStartup aStartup, in wstring aProfile); /** * A bit field containing profile items that this migrator * offers for import. * @param aProfile the profile that we are looking for available data * to import * @param aDoingStartup "true" if the profile is not currently being used. - * @returns bit field containing profile items (see above) + * @return bit field containing profile items (see above) + * @note a return value of 0 represents no items rather than ALL. */ unsigned short getMigrateData(in wstring aProfile, in boolean aDoingStartup); /** * Whether or not there is any data that can be imported from this * browser (i.e. whether or not it is installed, and there exists * a user profile) */ @@ -84,15 +85,15 @@ interface nsIBrowserProfileMigrator : ns * has multiple user profiles configured. */ readonly attribute boolean sourceHasMultipleProfiles; /** * An enumeration of available profiles. If the import source does * not support profiles, this attribute is null. */ - readonly attribute nsISupportsArray sourceProfiles; - + readonly attribute nsIArray sourceProfiles; + /** * The import source homepage. Returns null if not present/available */ readonly attribute AUTF8String sourceHomePageURL; };
--- a/browser/components/migration/src/ChromeProfileMigrator.js +++ b/browser/components/migration/src/ChromeProfileMigrator.js @@ -34,16 +34,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; +const Cr = Components.results; const LOCAL_FILE_CID = "@mozilla.org/file/local;1"; const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1"; const BUNDLE_MIGRATION = "chrome://browser/locale/migration/migration.properties"; const MIGRATE_ALL = 0x0000; const MIGRATE_SETTINGS = 0x0001; @@ -67,31 +68,31 @@ XPCOMUtils.defineLazyGetter(this, "bookm let strbundle = Services.strings.createBundle(BUNDLE_MIGRATION); let sourceNameChrome = strbundle.GetStringFromName("sourceNameChrome"); return strbundle.formatStringFromName("importedBookmarksFolder", [sourceNameChrome], 1); }); -/* +/** * Convert Chrome time format to Date object * * @param aTime * Chrome time * @return converted Date object * @note Google Chrome uses FILETIME / 10 as time. * FILETIME is based on same structure of Windows. */ function chromeTimeToDate(aTime) { return new Date((aTime * S100NS_PER_MS - S100NS_FROM1601TO1970 ) / 10000); } -/* +/** * Insert bookmark items into specific folder. * * @param aFolderId * id of folder where items will be inserted * @param aItems * bookmark items to be inserted */ function insertBookmarkItems(aFolderId, aItems) @@ -124,51 +125,64 @@ function ChromeProfileMigrator() } ChromeProfileMigrator.prototype = { _paths: { bookmarks : null, cookies : null, history : null, prefs : null, + userData : null, }, _homepageURL : null, _replaceBookmarks : false, + _sourceProfile: null, + _profilesCache: null, - /* + /** * Notify to observers to start migration * * @param aType * notification type such as MIGRATE_BOOKMARKS */ - _notifyStart : function Chrome_notifyStart(aType) { Services.obs.notifyObservers(null, "Migration:ItemBeforeMigrate", aType); this._pendingCount++; }, - /* + /** + * Notify observers that a migration error occured with an item + * + * @param aType + * notification type such as MIGRATE_BOOKMARKS + */ + _notifyError : function Chrome_notifyError(aType) + { + Services.obs.notifyObservers(null, "Migration:ItemError", aType); + }, + + /** * Notify to observers to finish migration for item * If all items are finished, it sends migration end notification. * * @param aType * notification type such as MIGRATE_BOOKMARKS */ _notifyCompleted : function Chrome_notifyIfCompleted(aType) { Services.obs.notifyObservers(null, "Migration:ItemAfterMigrate", aType); if (--this._pendingCount == 0) { // All items are migrated, so we have to send end notification. Services.obs.notifyObservers(null, "Migration:Ended", null); } }, - /* + /** * Migrating bookmark items */ _migrateBookmarks : function Chrome_migrateBookmarks() { this._notifyStart(MIGRATE_BOOKMARKS); try { PlacesUtils.bookmarks.runInBatchMode({ @@ -219,21 +233,22 @@ ChromeProfileMigrator.prototype = { } migrator._notifyCompleted(MIGRATE_BOOKMARKS); }); } }, null); } catch (e) { Cu.reportError(e); + this._notifyError(MIGRATE_BOOKMARKS); this._notifyCompleted(MIGRATE_BOOKMARKS); } }, - /* + /** * Migrating history */ _migrateHistory : function Chrome_migrateHistory() { this._notifyStart(MIGRATE_HISTORY); try { PlacesUtils.history.runInBatchMode({ @@ -293,21 +308,22 @@ ChromeProfileMigrator.prototype = { this._self._notifyCompleted(MIGRATE_HISTORY); } }); stmt.finalize(); } }, null); } catch (e) { Cu.reportError(e); + this._notifyError(MIGRATE_HISTORY); this._notifyCompleted(MIGRATE_HISTORY); } }, - /* + /** * Migrating cookies */ _migrateCookies : function Chrome_migrateCookies() { this._notifyStart(MIGRATE_COOKIES); try { // Access sqlite3 database of Chrome's cookie @@ -354,41 +370,44 @@ ChromeProfileMigrator.prototype = { handleCompletion : function(aReason) { this._db.asyncClose(); this._self._notifyCompleted(MIGRATE_COOKIES); }, }); stmt.finalize(); } catch (e) { Cu.reportError(e); + this._notifyError(MIGRATE_COOKIES); this._notifyCompleted(MIGRATE_COOKIES); } }, - /* + /** * nsIBrowserProfileMigrator interface implementation */ - /* + /** * Let's migrate all items * * @param aItems - * list of data items to migrate. but this is unused. + * list of data items to migrate. * @param aStartup * non-null if called during startup. * @param aProfile - * this is unused due to single profile support only + * profile directory name to migrate */ migrate : function Chrome_migrate(aItems, aStartup, aProfile) { if (aStartup) { aStartup.doStartup(); this._replaceBookmarks = true; } + this._sourceProfile = aProfile; + Services.obs.notifyObservers(null, "Migration:Started", null); // Reset panding count. If this count becomes 0, "Migration:Ended" // notification is sent this._pendingCount = 1; if (aItems & MIGRATE_HISTORY) this._migrateHistory(); @@ -402,112 +421,174 @@ ChromeProfileMigrator.prototype = { if (--this._pendingCount == 0) { // When async imports are immeditelly completed unfortunately, // this will be called. // Usually, this notification is sent by _notifyCompleted() Services.obs.notifyObservers(null, "Migration:Ended", null); } }, - /* + /** * return supported migration types * * @param aProfile - * this is unused due to single profile support only + * directory name of the profile * @param aDoingStartup * non-null if called during startup. * @return supported migration types */ getMigrateData: function Chrome_getMigrateData(aProfile, aDoingStartup) { -#ifdef XP_WIN - let chromepath = Services.dirsvc.get("LocalAppData", Ci.nsIFile).path + - "\\Google\\Chrome\\User Data\\Default\\"; -#elifdef XP_MACOSX - let chromepath = Services.dirsvc.get("Home", Ci.nsIFile).path + - "/Library/Application Support/Google/Chrome/Default/"; -#else - let chromepath = Services.dirsvc.get("Home", Ci.nsIFile).path + - "/.config/google-chrome/Default/"; -#endif + this._sourceProfile = aProfile; + let chromeProfileDir = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile); + chromeProfileDir.initWithPath(this._paths.userData + aProfile); let result = 0; + if (!chromeProfileDir.exists() || !chromeProfileDir.isReadable()) + return result; // bookmark and preference are JSON format try { - let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile); - file.initWithPath(chromepath + "Bookmarks"); + let file = chromeProfileDir.clone(); + file.append("Bookmarks"); if (file.exists()) { - this._paths.bookmarks = file.path + this._paths.bookmarks = file.path; result += MIGRATE_BOOKMARKS; } } catch (e) { Cu.reportError(e); } - if (!this._paths.prefs) - this._paths.prefs = chromepath + "Preferences"; + if (!this._paths.prefs) { + let file = chromeProfileDir.clone(); + file.append("Preferences"); + this._paths.prefs = file.path; + } // history and cookies are SQLite database try { - let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile); - file.initWithPath(chromepath + "History"); + let file = chromeProfileDir.clone(); + file.append("History"); if (file.exists()) { - this._paths.history = file.path + this._paths.history = file.path; result += MIGRATE_HISTORY; } } catch (e) { Cu.reportError(e); } try { - let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile); - file.initWithPath(chromepath + "Cookies"); + let file = chromeProfileDir.clone(); + file.append("Cookies"); if (file.exists()) { - this._paths.cookies = file.path + this._paths.cookies = file.path; result += MIGRATE_COOKIES; } } catch (e) { Cu.reportError(e); } return result; }, - /* + /** * Whether we support migration of Chrome * * @return true if supported */ get sourceExists() { - let result = this.getMigrateData(null, false); - return result != 0; +#ifdef XP_WIN + this._paths.userData = Services.dirsvc.get("LocalAppData", Ci.nsIFile).path + + "\\Google\\Chrome\\User Data\\"; +#elifdef XP_MACOSX + this._paths.userData = Services.dirsvc.get("Home", Ci.nsIFile).path + + "/Library/Application Support/Google/Chrome/"; +#else + this._paths.userData = Services.dirsvc.get("Home", Ci.nsIFile).path + + "/.config/google-chrome/"; +#endif + let result = 0; + try { + let userDataDir = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile); + userDataDir.initWithPath(this._paths.userData); + if (!userDataDir.exists() || !userDataDir.isReadable()) + return false; + + let profiles = this.sourceProfiles; + if (profiles.length < 1) + return false; + + // check that we can actually get data from the first profile + result = this.getMigrateData(profiles.queryElementAt(0, Ci.nsISupportsString), false); + } catch (e) { + Cu.reportError(e); + } + return result > 0; + }, + + get sourceHasMultipleProfiles() + { + return this.sourceProfiles.length > 1; }, - // Although Chrome supports multi-profiles, there is no way - // to get profile lists. - sourceHasMultipleProfiles: false, - sourceProfiles: null, + get sourceProfiles() + { + let profiles = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + try { + if (!this._profilesCache) { + let localState = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile); + // Local State is a JSON file that contains profile info. + localState.initWithPath(this._paths.userData + "Local State"); + if (!localState.exists()) + throw new Components.Exception("Chrome's 'Local State' file does not exist.", + Cr.NS_ERROR_FILE_NOT_FOUND); + if (!localState.isReadable()) + throw new Components.Exception("Chrome's 'Local State' file could not be read.", + Cr.NS_ERROR_FILE_ACCESS_DENIED); + let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream); + fstream.init(localState, -1, 0, 0); + let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(), + { charset: "UTF-8" }); + this._profilesCache = JSON.parse(inputStream).profile.info_cache; + } - /* + for (let index in this._profilesCache) { + let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); + str.data = index; + profiles.appendElement(str, false); + } + } catch (e) { + Cu.reportError("Error detecting Chrome profiles: " + e); + // if we weren't able to detect any profiles above, fallback to the Default profile. + if (profiles.length < 1) { + let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); + // the default profile name is "Default" + str.data = "Default"; + profiles.appendElement(str, false); + } + } + return profiles; + }, + + /** * Return home page URL * * @return home page URL */ get sourceHomePageURL() { try { if (this._homepageURL) return this._homepageURL; if (!this._paths.prefs) - this.getMigrateData(null, false); + this.getMigrateData(this._sourceProfile, false); // XXX reading and parsing JSON is synchronous. let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile); file.initWithPath(this._paths.prefs); let fstream = Cc[FILE_INPUT_STREAM_CID]. createInstance(Ci.nsIFileInputStream); fstream.init(file, -1, 0, 0); this._homepageURL = JSON.parse(
--- a/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp +++ b/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp @@ -49,17 +49,16 @@ #include "nsIURI.h" #include "nsNetUtil.h" #include "nsISupportsPrimitives.h" #include "nsAppDirectoryServiceDefs.h" #include "nsIRDFService.h" #include "nsIStringBundle.h" -#include "nsISupportsArray.h" #include "nsXPCOMCID.h" #define MIGRATION_BUNDLE "chrome://browser/locale/migration/migration.properties" #define BOOKMARKS_FILE_NAME NS_LITERAL_STRING("bookmarks.html") void SetUnicharPref(const char* aPref, const nsAString& aValue, nsIPrefBranch* aPrefs)
--- a/browser/components/migration/src/nsIEProfileMigrator.cpp +++ b/browser/components/migration/src/nsIEProfileMigrator.cpp @@ -60,17 +60,16 @@ #include "prlong.h" #include "nsICookieManager2.h" #include "nsIEProfileMigrator.h" #include "nsIFile.h" #include "nsILocalFile.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsISimpleEnumerator.h" -#include "nsISupportsArray.h" #include "nsIProfileMigrator.h" #include "nsIBrowserProfileMigrator.h" #include "nsIObserverService.h" #include "nsILocalFileWin.h" #include "nsAutoPtr.h" #include "prnetdb.h" @@ -481,17 +480,17 @@ nsIEProfileMigrator::GetSourceExists(boo NS_IMETHODIMP nsIEProfileMigrator::GetSourceHasMultipleProfiles(bool* aResult) { *aResult = false; return NS_OK; } NS_IMETHODIMP -nsIEProfileMigrator::GetSourceProfiles(nsISupportsArray** aResult) +nsIEProfileMigrator::GetSourceProfiles(nsIArray** aResult) { *aResult = nsnull; return NS_OK; } NS_IMETHODIMP nsIEProfileMigrator::GetSourceHomePageURL(nsACString& aResult) {
--- a/browser/components/migration/src/nsOperaProfileMigrator.cpp +++ b/browser/components/migration/src/nsOperaProfileMigrator.cpp @@ -165,56 +165,56 @@ nsOperaProfileMigrator::GetMigrateData(c aReplace, mOperaProfile, aResult); return NS_OK; } NS_IMETHODIMP nsOperaProfileMigrator::GetSourceExists(bool* aResult) { - nsCOMPtr<nsISupportsArray> profiles; + nsCOMPtr<nsIArray> profiles; GetSourceProfiles(getter_AddRefs(profiles)); if (profiles) { PRUint32 count; - profiles->Count(&count); + profiles->GetLength(&count); *aResult = count > 0; } else *aResult = false; return NS_OK; } NS_IMETHODIMP nsOperaProfileMigrator::GetSourceHasMultipleProfiles(bool* aResult) { - nsCOMPtr<nsISupportsArray> profiles; + nsCOMPtr<nsIArray> profiles; GetSourceProfiles(getter_AddRefs(profiles)); #ifdef XP_WIN if (profiles) { PRUint32 count; - profiles->Count(&count); + profiles->GetLength(&count); *aResult = count > 1; } else #endif *aResult = false; return NS_OK; } NS_IMETHODIMP -nsOperaProfileMigrator::GetSourceProfiles(nsISupportsArray** aResult) +nsOperaProfileMigrator::GetSourceProfiles(nsIArray** aResult) { if (!mProfiles) { nsresult rv; - mProfiles = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv); + mProfiles = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr<nsIProperties> fileLocator(do_GetService("@mozilla.org/file/directory_service;1")); nsCOMPtr<nsILocalFile> file; #ifdef XP_WIN fileLocator->Get(NS_WIN_APPDATA_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(file)); // Opera profile lives under %APP_DATA%\Opera\<operaver>\profile @@ -233,47 +233,47 @@ nsOperaProfileMigrator::GetSourceProfile bool isDirectory = false; curr->IsDirectory(&isDirectory); if (isDirectory) { nsCOMPtr<nsISupportsString> string(do_CreateInstance("@mozilla.org/supports-string;1")); nsAutoString leafName; curr->GetLeafName(leafName); string->SetData(leafName); - mProfiles->AppendElement(string); + mProfiles->AppendElement(string, false); } e->HasMoreElements(&hasMore); } #elif defined (XP_MACOSX) fileLocator->Get(NS_MAC_USER_LIB_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(file)); file->Append(NS_LITERAL_STRING("Preferences")); file->Append(OPERA_PREFERENCES_FOLDER_NAME); bool exists; file->Exists(&exists); if (exists) { nsCOMPtr<nsISupportsString> string(do_CreateInstance("@mozilla.org/supports-string;1")); string->SetData(OPERA_PREFERENCES_FOLDER_NAME); - mProfiles->AppendElement(string); + mProfiles->AppendElement(string, false); } #elif defined (XP_UNIX) fileLocator->Get(NS_UNIX_HOME_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(file)); file->Append(OPERA_PREFERENCES_FOLDER_NAME); bool exists; file->Exists(&exists); if (exists) { nsCOMPtr<nsISupportsString> string(do_CreateInstance("@mozilla.org/supports-string;1")); string->SetData(OPERA_PREFERENCES_FOLDER_NAME); - mProfiles->AppendElement(string); + mProfiles->AppendElement(string, false); } #endif } *aResult = mProfiles; NS_IF_ADDREF(*aResult); return NS_OK; }
--- a/browser/components/migration/src/nsOperaProfileMigrator.h +++ b/browser/components/migration/src/nsOperaProfileMigrator.h @@ -37,19 +37,19 @@ #ifndef operaprofilemigrator___h___ #define operaprofilemigrator___h___ #include "nsCOMPtr.h" #include "nsIBinaryInputStream.h" #include "nsIBrowserProfileMigrator.h" #include "nsIObserverService.h" -#include "nsISupportsArray.h" #include "nsStringAPI.h" #include "nsTArray.h" +#include "nsIMutableArray.h" #include "nsINavHistoryService.h" #include "nsIStringBundle.h" class nsICookieManager2; class nsILineInputStream; class nsILocalFile; class nsINIParser; class nsIPermissionManager; @@ -139,17 +139,17 @@ protected: nsIStringBundle* aBundle, PRInt64 aParentFolder); #endif // defined(XP_WIN) || (defined(XP_UNIX) && !defined(XP_MACOSX)) void GetOperaProfile(const PRUnichar* aProfile, nsILocalFile** aFile); private: nsCOMPtr<nsILocalFile> mOperaProfile; - nsCOMPtr<nsISupportsArray> mProfiles; + nsCOMPtr<nsIMutableArray> mProfiles; nsCOMPtr<nsIObserverService> mObserverService; }; class nsOperaCookieMigrator { public: nsOperaCookieMigrator(nsIInputStream* aSourceStream); virtual ~nsOperaCookieMigrator();
--- a/browser/components/migration/src/nsProfileMigrator.cpp +++ b/browser/components/migration/src/nsProfileMigrator.cpp @@ -40,17 +40,17 @@ #include "nsIBrowserProfileMigrator.h" #include "nsIComponentManager.h" #include "nsIDOMWindow.h" #include "nsILocalFile.h" #include "nsIObserverService.h" #include "nsIProperties.h" #include "nsIServiceManager.h" #include "nsISupportsPrimitives.h" -#include "nsISupportsArray.h" +#include "nsIMutableArray.h" #include "nsIToolkitProfile.h" #include "nsIToolkitProfileService.h" #include "nsIWindowWatcher.h" #include "nsCOMPtr.h" #include "nsBrowserCompsCID.h" #include "nsComponentManagerUtils.h" #include "nsDirectoryServiceDefs.h" @@ -122,23 +122,22 @@ nsProfileMigrator::Migrate(nsIProfileSta nsCOMPtr<nsISupportsCString> cstr (do_CreateInstance("@mozilla.org/supports-cstring;1")); if (!cstr) return NS_ERROR_OUT_OF_MEMORY; cstr->SetData(key); // By opening the Migration FE with a supplied bpm, it will automatically // migrate from it. nsCOMPtr<nsIWindowWatcher> ww(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); - nsCOMPtr<nsISupportsArray> params = - do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID); + nsCOMPtr<nsIMutableArray> params = do_CreateInstance(NS_ARRAY_CONTRACTID); if (!ww || !params) return NS_ERROR_FAILURE; - params->AppendElement(cstr); - params->AppendElement(bpm); - params->AppendElement(aStartup); + params->AppendElement(cstr, false); + params->AppendElement(bpm, false); + params->AppendElement(aStartup, false); nsCOMPtr<nsIDOMWindow> migrateWizard; return ww->OpenWindow(nsnull, MIGRATION_WIZARD_FE_URL, "_blank", MIGRATION_WIZARD_FE_FEATURES, params, getter_AddRefs(migrateWizard));
--- a/browser/components/migration/src/nsSafariProfileMigrator.cpp +++ b/browser/components/migration/src/nsSafariProfileMigrator.cpp @@ -54,17 +54,16 @@ #include "nsIProfileMigrator.h" #include "nsIProtocolHandler.h" #include "nsIRDFContainer.h" #include "nsIRDFDataSource.h" #include "nsIRDFRemoteDataSource.h" #include "nsIRDFService.h" #include "nsIServiceManager.h" #include "nsIStringBundle.h" -#include "nsISupportsArray.h" #include "nsISupportsPrimitives.h" #include "nsSafariProfileMigrator.h" #include "nsToolkitCompsCID.h" #include "nsNetUtil.h" #include "nsTArray.h" #include <Carbon/Carbon.h> @@ -197,17 +196,17 @@ NS_IMETHODIMP nsSafariProfileMigrator::GetSourceHasMultipleProfiles(bool* aResult) { // Safari only has one profile per-user. *aResult = false; return NS_OK; } NS_IMETHODIMP -nsSafariProfileMigrator::GetSourceProfiles(nsISupportsArray** aResult) +nsSafariProfileMigrator::GetSourceProfiles(nsIArray** aResult) { *aResult = nsnull; return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // nsSafariProfileMigrator
--- a/browser/components/migration/src/nsSafariProfileMigrator.h +++ b/browser/components/migration/src/nsSafariProfileMigrator.h @@ -36,17 +36,16 @@ * * ***** END LICENSE BLOCK ***** */ #ifndef safariprofilemigrator___h___ #define safariprofilemigrator___h___ #include "nsIBrowserProfileMigrator.h" #include "nsIObserverService.h" -#include "nsISupportsArray.h" #include "nsStringAPI.h" #include "nsINavHistoryService.h" #include <CoreFoundation/CoreFoundation.h> class nsIRDFDataSource; class nsSafariProfileMigrator : public nsIBrowserProfileMigrator,
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_pageinfo.js +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_pageinfo.js @@ -94,9 +94,10 @@ function test() { gBrowser.removeCurrentTab(); gBrowser.removeCurrentTab(); finish(); }); }); waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js @@ -40,16 +40,17 @@ function test() { // initialization let pb = Cc["@mozilla.org/privatebrowsing;1"]. getService(Ci.nsIPrivateBrowsingService); let cm = Cc["@mozilla.org/cookiemanager;1"]. getService(Ci.nsICookieManager); waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); const TEST_URL = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/title.sjs"; function waitForCleanup(aCallback) { // delete all cookies cm.removeAll(); // delete all history items waitForClearHistory(aCallback);
--- a/browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js +++ b/browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js @@ -13,16 +13,17 @@ // // 3. [backwards compat] Use a stringified state as formdata when opening about:sessionrestore // 3a. Make sure there are nodes in the tree on about:sessionrestore (skipped, checking formdata is sufficient) // 3b. Check that there are no backslashes in the formdata // 3c. Check that formdata (via getBrowserState) doesn't require JSON.parse function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]}; let crashState = { windows: [{ tabs: [{ entries: [{ url: "about:mozilla" }] }]}]}; let pagedata = { url: "about:sessionrestore", formdata: { "#sessionData": crashState } }; let state = { windows: [{ tabs: [{ entries: [pagedata] }] }] };
--- a/browser/components/sessionstore/test/browser/browser_586147.js +++ b/browser/components/sessionstore/test/browser/browser_586147.js @@ -39,16 +39,17 @@ function observeOneRestore(callback) { Services.obs.addObserver(function() { Services.obs.removeObserver(arguments.callee, topic, false); callback(); }, topic, false); }; function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); // There should be one tab when we start the test let [origTab] = gBrowser.visibleTabs; let hiddenTab = gBrowser.addTab(); is(gBrowser.visibleTabs.length, 2, "should have 2 tabs before hiding"); gBrowser.showOnlyTheseTabs([origTab]); is(gBrowser.visibleTabs.length, 1, "only 1 after hiding");
--- a/browser/components/sessionstore/test/browser/browser_607016.js +++ b/browser/components/sessionstore/test/browser/browser_607016.js @@ -47,16 +47,17 @@ function cleanup() { } catch (e) {} ss.setBrowserState(stateBackup); executeSoon(finish); } function test() { /** Bug 607016 - If a tab is never restored, attributes (eg. hidden) aren't updated correctly **/ waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); // Set the pref to true so we know exactly how many tabs should be restoring at // any given time. This guarantees that a finishing load won't start another. Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true); // We have our own progress listener for this test, which we'll attach before our state is set let progressListener = { onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
--- a/browser/components/tabview/groupitems.js +++ b/browser/components/tabview/groupitems.js @@ -143,16 +143,17 @@ function GroupItem(listOfEls, options) { .html(html) .appendTo($container); this.$closeButton = iQ('<div>') .addClass('close') .click(function() { self.closeAll(); }) + .attr("title", tabviewString("groupItem.closeGroup")) .appendTo($container); // ___ Title this.$titleContainer = iQ('.title-container', this.$titlebar); this.$title = iQ('.name', this.$titlebar); this.$titleShield = iQ('.title-shield', this.$titlebar); this.setTitle(options.title); @@ -193,31 +194,33 @@ function GroupItem(listOfEls, options) { (self.$title)[0].select(); self._titleFocused = true; } }) .mousedown(function(e) { e.stopPropagation(); }) .keypress(handleKeyPress) - .keyup(handleKeyUp); + .keyup(handleKeyUp) + .attr("title", tabviewString("groupItem.defaultName")); this.$titleShield .mousedown(function(e) { self.lastMouseDownTarget = (Utils.isLeftClick(e) ? e.target : null); }) .mouseup(function(e) { var same = (e.target == self.lastMouseDownTarget); self.lastMouseDownTarget = null; if (!same) return; if (!self.isDragging) self.focusTitle(); - }); + }) + .attr("title", tabviewString("groupItem.defaultName")); if (options.focusTitle) this.focusTitle(); // ___ Stack Expander this.$expander = iQ("<div/>") .addClass("stackExpander") .appendTo($container) @@ -899,16 +902,17 @@ GroupItem.prototype = Utils.extend(new I .attr("type", "button") .attr("data-group-id", this.id) .appendTo("body"); iQ("<span/>") .text(tabviewString("groupItem.undoCloseGroup")) .appendTo(this.$undoContainer); let undoClose = iQ("<span/>") .addClass("close") + .attr("title", tabviewString("groupItem.discardClosedGroup")) .appendTo(this.$undoContainer); this.$undoContainer.css({ left: this.bounds.left + this.bounds.width/2 - iQ(self.$undoContainer).width()/2, top: this.bounds.top + this.bounds.height/2 - iQ(self.$undoContainer).height()/2, "-moz-transform": "scale(.1)", opacity: 0 }); @@ -1138,17 +1142,17 @@ GroupItem.prototype = Utils.extend(new I // if a blank tab is selected while restoring a tab the blank tab gets // removed. we need to keep the group alive for the restored tab. if (item.isRemovedAfterRestore) options.dontClose = true; let closed = options.dontClose ? false : this.closeIfEmpty(); if (closed || - (this._children.length == 0 && !gBrowser.selectedTab.pinned && + (this._children.length == 0 && !gBrowser._numPinnedTabs && !item.isDragging)) { this._makeLastActiveGroupItemActive(); } else if (!options.dontArrange) { this.arrange({animate: !options.immediately}); this._unfreezeItemSize({dontArrange: true}); } this._sendToSubscribers("childRemoved",{ groupItemId: this.id, item: item }); @@ -2225,39 +2229,41 @@ let GroupItems = { }; new GroupItem([], Utils.extend({}, data, options)); } } } toClose.forEach(function(groupItem) { - // All remaining children in to-be-closed groups are re-used by - // session restore. Reconnect them so that they're put into their - // right groups. - let children = groupItem.getChildren().concat(); + // all tabs still existing in closed groups will be moved to new + // groups. prepare them to be reconnected later. + groupItem.getChildren().forEach(function (tabItem) { + if (tabItem.parent.hidden) + iQ(tabItem.container).show(); - children.forEach(function (tabItem) { - if (tabItem.parent && tabItem.parent.hidden) - iQ(tabItem.container).show(); + tabItem._reconnected = false; // sanity check the tab's groupID let tabData = Storage.getTabData(tabItem.tab); - let parentGroup = GroupItems.groupItem(tabData.groupID); + + if (tabData) { + let parentGroup = GroupItems.groupItem(tabData.groupID); - // correct the tab's groupID if necessary - if (!parentGroup || -1 < toClose.indexOf(parentGroup)) { - tabData.groupID = activeGroupId || Object.keys(groupItemData)[0]; - Storage.saveTab(tabItem.tab, tabData); + // the tab's group id could be invalid or point to a non-existing + // group. correct it by assigning the active group id or the first + // group of the just restored session. + if (!parentGroup || -1 < toClose.indexOf(parentGroup)) { + tabData.groupID = activeGroupId || Object.keys(groupItemData)[0]; + Storage.saveTab(tabItem.tab, tabData); + } } - - tabItem._reconnected = false; - tabItem._reconnect(); }); + // this closes the group but not its children groupItem.close({immediately: true}); }); } // set active group item if (activeGroupId) { let activeGroupItem = this.groupItem(activeGroupId); if (activeGroupItem) @@ -2461,20 +2467,24 @@ let GroupItems = { let tabItems = this._activeGroupItem._children; gBrowser.showOnlyTheseTabs(tabItems.map(function(item) item.tab)); }, // ---------- // Function: updateActiveGroupItemAndTabBar // Sets active TabItem and GroupItem, and updates tab bar appropriately. - updateActiveGroupItemAndTabBar: function GroupItems_updateActiveGroupItemAndTabBar(tabItem) { + // Parameters: + // tabItem - the tab item + // options - is passed to UI.setActive() directly + updateActiveGroupItemAndTabBar: + function GroupItems_updateActiveGroupItemAndTabBar(tabItem, options) { Utils.assertThrow(tabItem && tabItem.isATabItem, "tabItem must be a TabItem"); - UI.setActive(tabItem); + UI.setActive(tabItem, options); this._updateTabBar(); }, // ---------- // Function: getNextGroupItemTab // Paramaters: // reverse - the boolean indicates the direction to look for the next groupItem. // Returns the <tabItem>. If nothing is found, return null.
--- a/browser/components/tabview/search.js +++ b/browser/components/tabview/search.js @@ -426,23 +426,25 @@ let Search = { iQ("#search").hide(); iQ("#searchshade").hide().mousedown(function Search_init_shade_mousedown(event) { if (event.target.id != "searchbox" && !self._blockClick) self.hide(); }); iQ("#searchbox").keyup(function Search_init_box_keyup() { self.perform(); - }); + }) + .attr("title", tabviewString("button.searchTabs")); iQ("#searchbutton").mousedown(function Search_init_button_mousedown() { self._initiatedBy = "buttonclick"; self.ensureShown(); self.switchToInMode(); - }); + }) + .attr("title", tabviewString("button.searchTabs")); window.addEventListener("focus", function Search_init_window_focus() { if (self.isEnabled()) { self._blockClick = true; setTimeout(function() { self._blockClick = false; }, 0); }
--- a/browser/components/tabview/tabitems.js +++ b/browser/components/tabview/tabitems.js @@ -143,16 +143,18 @@ function TabItem(tab, options) { } else { if (!Items.item(this).isDragging) self.zoomIn(); } }); this.droppable(true); + this.$close.attr("title", tabbrowserString("tabs.closeTab")); + TabItems.register(this); // ___ reconnect to data from Storage if (!TabItems.reconnectingPaused()) this._reconnect(options); }; TabItem.prototype = Utils.extend(new Item(), new Subscribable(), { @@ -199,17 +201,25 @@ TabItem.prototype = Utils.extend(new Ite // // Parameters: // tabData - the tab data // imageData - the image data showCachedData: function TabItem_showCachedData(tabData, imageData) { this._cachedImageData = imageData; this.$cachedThumb.attr("src", this._cachedImageData).show(); this.$canvas.css({opacity: 0}); - this.$tabTitle.text(tabData.title ? tabData.title : ""); + let label = ""; + let title; + if (tabData.title) { + label = tabData.title; + title = label + "\n" + tabData.url; + } else { + title = tabData.url; + } + this.$tabTitle.text(label).attr("title", title); this._sendToSubscribers("showingCachedData"); }, // ---------- // Function: hideCachedData // Hides the cached data i.e. image and title and show the canvas. hideCachedData: function TabItem_hideCachedData() { @@ -998,20 +1008,20 @@ let TabItems = { // ___ remove from waiting list now that we have no other // early returns this._tabsWaitingForUpdate.remove(tab); // ___ URL let tabUrl = tab.linkedBrowser.currentURI.spec; if (tabUrl != tabItem.url) { - let oldURL = tabItem.url; tabItem.url = tabUrl; tabItem.save(); } + tabItem.$container.attr("title", label + "\n" + tabUrl); // ___ Make sure the tab is complete and ready for updating. if (!this.isComplete(tab) && (!options || !options.force)) { // If it's incomplete, stick it on the end of the queue this._tabsWaitingForUpdate.push(tab); return; }
--- a/browser/components/tabview/tabview.js +++ b/browser/components/tabview/tabview.js @@ -8,18 +8,23 @@ const Cr = Components.results; Cu.import("resource:///modules/tabview/utils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyGetter(this, "tabviewBundle", function() { return Services.strings. createBundle("chrome://browser/locale/tabview.properties"); }); +XPCOMUtils.defineLazyGetter(this, "tabbrowserBundle", function() { + return Services.strings. + createBundle("chrome://browser/locale/tabbrowser.properties"); +}); function tabviewString(name) tabviewBundle.GetStringFromName('tabview.' + name); +function tabbrowserString(name) tabbrowserBundle.GetStringFromName(name); XPCOMUtils.defineLazyGetter(this, "gPrefBranch", function() { return Services.prefs.getBranch("browser.panorama."); }); XPCOMUtils.defineLazyGetter(this, "gPrivateBrowsing", function() { return Cc["@mozilla.org/privatebrowsing;1"]. getService(Ci.nsIPrivateBrowsingService);
--- a/browser/components/tabview/test/Makefile.in +++ b/browser/components/tabview/test/Makefile.in @@ -82,18 +82,19 @@ include $(topsrcdir)/config/rules.mk browser_tabview_bug600645.js \ browser_tabview_bug600812.js \ browser_tabview_bug602432.js \ browser_tabview_bug604098.js \ browser_tabview_bug606657.js \ browser_tabview_bug606905.js \ browser_tabview_bug607108.js \ browser_tabview_bug608037.js \ + browser_tabview_bug608153.js \ + browser_tabview_bug608158.js \ browser_tabview_bug608184.js \ - browser_tabview_bug608158.js \ browser_tabview_bug608405.js \ browser_tabview_bug610208.js \ browser_tabview_bug610242.js \ browser_tabview_bug612470.js \ browser_tabview_bug613541.js \ browser_tabview_bug616729.js \ browser_tabview_bug616967.js \ browser_tabview_bug618816.js \ @@ -160,16 +161,18 @@ include $(topsrcdir)/config/rules.mk browser_tabview_bug677310.js \ browser_tabview_bug679853.js \ browser_tabview_bug681599.js \ browser_tabview_bug685476.js \ browser_tabview_bug685692.js \ browser_tabview_bug686654.js \ browser_tabview_bug697390.js \ browser_tabview_bug705621.js \ + browser_tabview_bug706430.js \ + browser_tabview_bug706736.js \ browser_tabview_click_group.js \ browser_tabview_dragdrop.js \ browser_tabview_exit_button.js \ browser_tabview_expander.js \ browser_tabview_firstrun_pref.js \ browser_tabview_group.js \ browser_tabview_launch.js \ browser_tabview_multiwindow_search.js \
new file mode 100644 --- /dev/null +++ b/browser/components/tabview/test/browser_tabview_bug608153.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + waitForExplicitFinish(); + + let pinnedTab = gBrowser.addTab(); + gBrowser.pinTab(pinnedTab); + + registerCleanupFunction(function() { + gBrowser.unpinTab(pinnedTab); + while (gBrowser.tabs[1]) + gBrowser.removeTab(gBrowser.tabs[1]); + hideTabView(); + }); + + showTabView(function() { + let cw = TabView.getContentWindow(); + let groupItemOne = cw.GroupItems.groupItems[0]; + let groupItemTwo = createGroupItemWithBlankTabs(window, 250, 250, 40, 1); + + is(cw.GroupItems.groupItems.length, 2, "Two group items"); + + hideTabView(function() { + gBrowser.selectedTab = pinnedTab; + is(cw.GroupItems.getActiveGroupItem(), groupItemTwo, "Group two is active"); + is(gBrowser.selectedTab, pinnedTab, "Selected tab is the pinned tab"); + + goToNextGroup(); + is(cw.GroupItems.getActiveGroupItem(), groupItemOne, "Group one is active"); + is(gBrowser.selectedTab, pinnedTab, "Selected tab is the pinned tab"); + + finish(); + }); + }); +} + +function goToNextGroup() { + let utils = + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + + const masks = Ci.nsIDOMNSEvent; + let mval = 0; + mval |= masks.CONTROL_MASK; + + utils.sendKeyEvent("keypress", 0, 96, mval); +}
--- a/browser/components/tabview/test/browser_tabview_bug705621.js +++ b/browser/components/tabview/test/browser_tabview_bug705621.js @@ -10,17 +10,17 @@ function test() { }); let cw = win.TabView.getContentWindow(); let groupItemOne = cw.GroupItems.groupItems[0]; is(groupItemOne.getChildren().length, 1, "Group one has 1 tab item"); let groupItemTwo = createGroupItemWithBlankTabs(win, 300, 300, 40, 1); - is(groupItemTwo.getChildren().length, 1, "Group two has 2 tab items"); + is(groupItemTwo.getChildren().length, 1, "Group two has 1 tab item"); whenTabViewIsHidden(function() { executeSoon(function() { win.gBrowser.removeTab(win.gBrowser.selectedTab); is(cw.UI.getActiveTab(), groupItemOne.getChild(0), "TabItem in Group one is selected"); finish(); }); }, win);
new file mode 100644 --- /dev/null +++ b/browser/components/tabview/test/browser_tabview_bug706430.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let state1 = { + windows: [{ + tabs: [{ + entries: [{ url: "about:blank#1" }], + hidden: true, + extData: {"tabview-tab": '{"url":"about:blank#1","groupID":1,"bounds":{"left":120,"top":20,"width":20,"height":20}}'} + },{ + entries: [{ url: "about:blank#2" }], + hidden: false, + extData: {"tabview-tab": '{"url":"about:blank#2","groupID":2,"bounds":{"left":20,"top":20,"width":20,"height":20}}'}, + }], + selected: 2, + extData: { + "tabview-groups": '{"nextID":3,"activeGroupId":2, "totalNumber":2}', + "tabview-group": + '{"1":{"bounds":{"left":15,"top":5,"width":280,"height":232},"id":1},' + + '"2":{"bounds":{"left":309,"top":5,"width":267,"height":226},"id":2}}' + } + }] +}; + +let state2 = { + windows: [{ + tabs: [{entries: [{ url: "about:blank#1" }], hidden: true}, + {entries: [{ url: "about:blank#2" }], hidden: false}], + selected: 2 + }] +}; + +let ss = Cc["@mozilla.org/browser/sessionstore;1"] + .getService(Ci.nsISessionStore); + +function test() { + waitForExplicitFinish(); + + newWindowWithState(state1, function (win) { + registerCleanupFunction(function () win.close()); + + showTabView(function () { + let cw = win.TabView.getContentWindow(); + let [group1, group2] = cw.GroupItems.groupItems; + let [tab1, tab2] = win.gBrowser.tabs; + + checkUrl(group1.getChild(0), "about:blank#1", "tab1 is in first group"); + checkUrl(group2.getChild(0), "about:blank#2", "tab2 is in second group"); + + whenWindowStateReady(win, function () { + let groups = cw.GroupItems.groupItems; + is(groups.length, 1, "one groupItem"); + is(groups[0].getChildren().length, 2, "single groupItem has two children"); + + finish(); + }); + + ss.setWindowState(win, JSON.stringify(state2), true); + }, win); + }); +} + +function checkUrl(aTabItem, aUrl, aMsg) { + is(aTabItem.tab.linkedBrowser.currentURI.spec, aUrl, aMsg); +}
new file mode 100644 --- /dev/null +++ b/browser/components/tabview/test/browser_tabview_bug706736.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + waitForExplicitFinish(); + + newWindowWithTabView(function(win) { + registerCleanupFunction(function() { + win.close(); + }); + + let cw = win.TabView.getContentWindow(); + + let groupItemOne = cw.GroupItems.groupItems[0]; + is(groupItemOne.getChildren().length, 1, "Group one has 1 tab item"); + + let groupItemTwo = createGroupItemWithBlankTabs(win, 300, 300, 40, 1); + is(groupItemTwo.getChildren().length, 1, "Group two has 1 tab items"); + + whenTabViewIsHidden(function() { + win.gBrowser.removeTab(win.gBrowser.selectedTab); + executeSoon(function() { + win.undoCloseTab(); + + groupItemTwo.addSubscriber("childAdded", function onChildAdded(data) { + groupItemTwo.removeSubscriber("childAdded", onChildAdded); + + is(groupItemOne.getChildren().length, 1, "Group one still has 1 tab item"); + is(groupItemTwo.getChildren().length, 1, "Group two still has 1 tab item"); + }); + + finish(); + }); + }, win); + groupItemTwo.getChild(0).zoomIn(); + }, function(win) { + let newTab = win.gBrowser.addTab(); + win.gBrowser.pinTab(newTab); + }); +}
--- a/browser/components/tabview/ui.js +++ b/browser/components/tabview/ui.js @@ -190,17 +190,18 @@ let UI = { // ___ currentTab this._currentTab = gBrowser.selectedTab; // ___ exit button iQ("#exit-button").click(function() { self.exit(); self.blurAll(); - }); + }) + .attr("title", tabviewString("button.exitTabGroups")); // When you click on the background/empty part of TabView, // we create a new groupItem. iQ(gTabViewFrame.contentDocument).mousedown(function(e) { if (iQ(":focus").length > 0) { iQ(":focus").each(function(element) { // don't fire blur event if the same input element is clicked. if (e.target != element && element.nodeName == "INPUT") @@ -460,17 +461,18 @@ let UI = { // options // dontSetActiveTabInGroup bool for not setting active tab in group setActive: function UI_setActive(item, options) { Utils.assert(item, "item must be given"); if (item.isATabItem) { if (item.parent) GroupItems.setActiveGroupItem(item.parent); - this._setActiveTab(item); + if (!options || !options.dontSetActiveTabInGroup) + this._setActiveTab(item); } else { GroupItems.setActiveGroupItem(item); if (!options || !options.dontSetActiveTabInGroup) { let activeTab = item.getActiveTab() if (activeTab) this._setActiveTab(activeTab); } }
--- a/browser/devtools/highlighter/inspector.jsm +++ b/browser/devtools/highlighter/inspector.jsm @@ -2215,17 +2215,19 @@ InspectorProgressListener.prototype = { GetStringFromName("confirmNavigationAway.buttonLeave"), accessKey: this.IUI.strings. GetStringFromName("confirmNavigationAway.buttonLeaveAccesskey"), callback: function onButtonLeave() { if (aRequest) { aRequest.resume(); aRequest = null; this.IUI.closeInspectorUI(); + return true; } + return false; }.bind(this), }, { id: "inspector.confirmNavigationAway.buttonStay", label: this.IUI.strings. GetStringFromName("confirmNavigationAway.buttonStay"), accessKey: this.IUI.strings. GetStringFromName("confirmNavigationAway.buttonStayAccesskey"),
--- a/browser/devtools/highlighter/test/browser_inspector_bug_665880.js +++ b/browser/devtools/highlighter/test/browser_inspector_bug_665880.js @@ -1,15 +1,16 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); let doc; let objectNode; gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document;
--- a/browser/devtools/highlighter/test/browser_inspector_bug_690361.js +++ b/browser/devtools/highlighter/test/browser_inspector_bug_690361.js @@ -122,16 +122,17 @@ function finishInspectorTests() gBrowser.removeCurrentTab(); finish(); }); } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document; waitForFocus(createDocument, content); }, true); content.location = "data:text/html,basic tests for inspector";
--- a/browser/devtools/highlighter/test/browser_inspector_duplicate_ruleview.js +++ b/browser/devtools/highlighter/test/browser_inspector_duplicate_ruleview.js @@ -106,16 +106,17 @@ function inspectorRuleTrap() Services.obs.removeObserver(inspectorRuleTrap, InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false); is(InspectorUI.ruleView.doc.documentElement.children.length, 1, "RuleView elements.length == 1"); } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); tab1 = gBrowser.addTab(); gBrowser.selectedTab = tab1; gBrowser.selectedBrowser.addEventListener("load", function(evt) { gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); waitForFocus(inspectorTabOpen1, content); }, true);
--- a/browser/devtools/highlighter/test/browser_inspector_infobar.js +++ b/browser/devtools/highlighter/test/browser_inspector_infobar.js @@ -1,14 +1,15 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); let doc; let nodes; let cursor; gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function onload() { gBrowser.selectedBrowser.removeEventListener("load", onload, true);
--- a/browser/devtools/highlighter/test/browser_inspector_initialization.js +++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js @@ -213,16 +213,17 @@ function finishInspectorTests() gBrowser.removeCurrentTab(); finish(); } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document; waitForFocus(createDocument, content); }, true); content.location = "data:text/html,basic tests for inspector";
--- a/browser/devtools/highlighter/test/browser_inspector_registertools.js +++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js @@ -208,16 +208,17 @@ function finishUp() { gBrowser.removeCurrentTab(); InspectorUI.initTools = initToolsMethod; finish(); } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document; waitForFocus(createDocument, content); }, true); content.location = "data:text/html,registertool tests for inspector";
--- a/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js +++ b/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js @@ -132,16 +132,17 @@ function ruleViewOpened2() gBrowser.removeCurrentTab(); InspectorUI.closeInspectorUI(); finish(); } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); tab1 = gBrowser.addTab(); gBrowser.selectedTab = tab1; gBrowser.selectedBrowser.addEventListener("load", function(evt) { gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); waitForFocus(inspectorTabOpen1, content); }, true);
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js +++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js @@ -258,16 +258,17 @@ function inspectorTabUnload1(evt) InspectorUI.closeInspectorUI(); gBrowser.removeCurrentTab(); finish(); } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); tab1 = gBrowser.addTab(); gBrowser.selectedTab = tab1; gBrowser.selectedBrowser.addEventListener("load", function(evt) { gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); waitForFocus(inspectorTabOpen1, content); }, true);
--- a/browser/devtools/highlighter/test/browser_inspector_treeSelection.js +++ b/browser/devtools/highlighter/test/browser_inspector_treeSelection.js @@ -100,16 +100,17 @@ function finishUp() { doc = h1 = null; gBrowser.removeCurrentTab(); finish(); } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document; waitForFocus(createDocument, content); }, true); content.location = "data:text/html,basic tests for inspector";
--- a/browser/devtools/styleinspector/test/browser/browser_bug683672.js +++ b/browser/devtools/styleinspector/test/browser/browser_bug683672.js @@ -9,16 +9,17 @@ let stylePanel; const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/test/browser/browser_bug683672.html"; Cu.import("resource:///modules/devtools/CssHtmlTree.jsm"); function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); addTab(TEST_URI); browser.addEventListener("load", tabLoaded, true); } function tabLoaded() { browser.removeEventListener("load", tabLoaded, true); doc = content.document;
--- a/browser/devtools/styleinspector/test/browser/browser_styleinspector.js +++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector.js @@ -74,16 +74,17 @@ function finishUp() doc = stylePanel = null; gBrowser.removeCurrentTab(); finish(); } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function(evt) { gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); doc = content.document; waitForFocus(createDocument, content); }, true); content.location = "data:text/html,basic style inspector tests";
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_580030_errors_after_page_reload.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_580030_errors_after_page_reload.js @@ -38,16 +38,17 @@ * * ***** END LICENSE BLOCK ***** */ // Tests that errors still show up in the Web Console after a page reload. const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//browser/test-error.html"; function test() { + expectUncaughtException(); addTab(TEST_URI); browser.addEventListener("load", onLoad, true); } // see bug 580030: the error handler fails silently after page reload. // https://bugzilla.mozilla.org/show_bug.cgi?id=580030 function onLoad(aEvent) { browser.removeEventListener(aEvent.type, arguments.callee, true);
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_582201_duplicate_errors.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_582201_duplicate_errors.js @@ -39,29 +39,31 @@ * ***** END LICENSE BLOCK ***** */ // Tests that exceptions thrown by content don't show up twice in the Web // Console. const TEST_DUPLICATE_ERROR_URI = "http://example.com/browser/browser/devtools/webconsole/test//browser/test-duplicate-error.html"; function test() { + expectUncaughtException(); addTab(TEST_DUPLICATE_ERROR_URI); browser.addEventListener("DOMContentLoaded", testDuplicateErrors, false); } function testDuplicateErrors() { browser.removeEventListener("DOMContentLoaded", testDuplicateErrors, false); openConsole(); HUDService.getHudByWindow(content).jsterm.clearOutput(); Services.console.registerListener(consoleObserver); + expectUncaughtException(); content.location.reload(); } var consoleObserver = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), observe: function (aMessage) {
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_597136_external_script_errors.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_597136_external_script_errors.js @@ -24,16 +24,17 @@ function tabLoaded(aEvent) { browser.addEventListener("load", contentLoaded, true); content.location.reload(); } function contentLoaded(aEvent) { browser.removeEventListener("load", contentLoaded, true); let button = content.document.querySelector("button"); + expectUncaughtException(); EventUtils.sendMouseEvent({ type: "click" }, button, content); executeSoon(buttonClicked); } function buttonClicked() { let outputNode = HUDService.getHudByWindow(content).outputNode; let msg = "the error from the external script was logged";
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_597756_reopen_closed_tab.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_597756_reopen_closed_tab.js @@ -13,16 +13,17 @@ const TEST_URI = "http://example.com/bro let newTabIsOpen = false; function tabLoaded(aEvent) { gBrowser.selectedBrowser.removeEventListener(aEvent.type, arguments.callee, true); HUDService.activateHUDForContext(gBrowser.selectedTab); gBrowser.selectedBrowser.addEventListener("load", tabReloaded, true); + expectUncaughtException(); content.location.reload(); } function tabReloaded(aEvent) { gBrowser.selectedBrowser.removeEventListener(aEvent.type, arguments.callee, true); let hudId = HUDService.getHudIdByWindow(content); let HUD = HUDService.hudReferences[hudId]; @@ -38,22 +39,24 @@ function tabReloaded(aEvent) { } let newTab = gBrowser.addTab(); gBrowser.removeCurrentTab(); gBrowser.selectedTab = newTab; newTabIsOpen = true; gBrowser.selectedBrowser.addEventListener("load", tabLoaded, true); + expectUncaughtException(); content.location = TEST_URI; }); } function testEnd() { gBrowser.removeCurrentTab(); executeSoon(finishTest); } function test() { + expectUncaughtException(); addTab(TEST_URI); browser.addEventListener("load", tabLoaded, true); }
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_601177_log_levels.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_601177_log_levels.js @@ -59,12 +59,13 @@ function test() browser.removeEventListener(aEvent.type, arguments.callee, true); openConsole(); browser.addEventListener("load", function(aEvent) { browser.removeEventListener(aEvent.type, arguments.callee, true); executeSoon(onContentLoaded); }, true); + expectUncaughtException(); content.location = TEST_URI; }, true); }
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_618078_network_exceptions.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_618078_network_exceptions.js @@ -85,13 +85,14 @@ function test() let hudId = HUDService.getHudIdByWindow(content); hud = HUDService.hudReferences[hudId]; Services.console.registerListener(TestObserver); registerCleanupFunction(testEnd); executeSoon(function() { + expectUncaughtException(); content.location = TEST_URI; }); }, true); }
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_644419_log_limits.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_644419_log_limits.js @@ -20,16 +20,17 @@ function test() { function onLoad(aEvent) { browser.removeEventListener(aEvent.type, arguments.callee, true); openConsole(); gHudId = HUDService.getHudIdByWindow(content); browser.addEventListener("load", testWebDevLimits, true); + expectUncaughtException(); content.location = TEST_URI; } function testWebDevLimits(aEvent) { browser.removeEventListener(aEvent.type, arguments.callee, true); gOldPref = Services.prefs.getIntPref("devtools.hud.loglimit.console"); Services.prefs.setIntPref("devtools.hud.loglimit.console", 10); @@ -65,16 +66,17 @@ function testJsLimits(aEvent) { // Find the sentinel entry. findLogEntry("testing JS limits"); // Fill the log with JS errors. let head = content.document.getElementsByTagName("head")[0]; for (let i = 0; i < 11; i++) { var script = content.document.createElement("script"); script.text = "fubar" + i + ".bogus(6);"; + expectUncaughtException(); head.insertBefore(script, head.firstChild); } executeSoon(function() { testLogEntry(outputNode, "fubar0 is not defined", "first message is pruned", false, true); findLogEntry("fubar1 is not defined"); // Check if the sentinel entry is still there. findLogEntry("testing JS limits");
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_network_panel.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_network_panel.js @@ -412,17 +412,20 @@ function testGen() { // Test a response with a content type that can't be displayed in the // NetworkPanel. httpActivity.response.header["Content-Type"] = "application/x-shockwave-flash"; networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity); networkPanel.isDoneCallback = function NP_doneCallback() { networkPanel.isDoneCallback = null; - testDriver.next(); + try { + testDriver.next(); + } catch (e if e instanceof StopIteration) { + } } yield; checkIsVisible(networkPanel, { requestBody: false, requestFormData: true, requestCookie: true, @@ -473,10 +476,10 @@ function testGen() { if (networkPanel.document.getElementById("responseBodyUnknownTypeContent").textContent !== "") checkNodeContent(networkPanel, "responseBodyUnknownTypeContent", responseString); else ok(true, "Flash not installed"); networkPanel.panel.hidePopup(); */ // All done! - finishTest(); + finish(); }
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_view_source.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_view_source.js @@ -2,16 +2,17 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ // Tests that source URLs in the Web Console can be clicked to display the // standard View Source window. const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//browser/test-error.html"; function test() { + expectUncaughtException(); addTab(TEST_URI); browser.addEventListener("DOMContentLoaded", testViewSource, false); } function testViewSource() { browser.removeEventListener("DOMContentLoaded", testViewSource, false); openConsole();
--- a/browser/installer/removed-files.in +++ b/browser/installer/removed-files.in @@ -1309,34 +1309,34 @@ xpicleanup@BIN_SUFFIX@ #endif #ifdef XP_WIN components/brwsrcmp.dll components/jsd3250.dll components/nsPostUpdateWin.js js3250.dll plugins/npnul32.dll #if _MSC_VER != 1400 - @BINPATH@/Microsoft.VC80.CRT.manifest - @BINPATH@/msvcm80.dll - @BINPATH@/msvcp80.dll - @BINPATH@/msvcr80.dll + Microsoft.VC80.CRT.manifest + msvcm80.dll + msvcp80.dll + msvcr80.dll #endif #if _MSC_VER != 1500 - @BINPATH@/Microsoft.VC90.CRT.manifest - @BINPATH@/msvcm90.dll - @BINPATH@/msvcp90.dll - @BINPATH@/msvcr90.dll + Microsoft.VC90.CRT.manifest + msvcm90.dll + msvcp90.dll + msvcr90.dll #endif #if _MSC_VER != 1600 - @BINPATH@/msvcp100.dll - @BINPATH@/msvcr100.dll + msvcp100.dll + msvcr100.dll #endif #if _MSC_VER != 1700 - @BINPATH@/msvcp110.dll - @BINPATH@/msvcr110.dll + msvcp110.dll + msvcr110.dll #endif mozcrt19.dll mozcpp19.dll #endif @DLL_PREFIX@xpcom_core@DLL_SUFFIX@ components/@DLL_PREFIX@jar50@DLL_SUFFIX@ #ifdef XP_WIN components/xpinstal.dll
--- a/browser/locales/en-US/chrome/browser/migration/migration.dtd +++ b/browser/locales/en-US/chrome/browser/migration/migration.dtd @@ -4,18 +4,16 @@ <!ENTITY importFrom.label "Import Options, Bookmarks, History, Passwords and other data from:"> <!ENTITY importFromUnix.label "Import Preferences, Bookmarks, History, Passwords and other data from:"> <!ENTITY importFromBookmarks.label "Import Bookmarks from:"> <!ENTITY importFromIE.label "Microsoft Internet Explorer"> <!ENTITY importFromIE.accesskey "M"> <!ENTITY importFromNothing.label "Don't import anything"> <!ENTITY importFromNothing.accesskey "D"> -<!ENTITY importFromSeamonkey.label "Netscape 6, 7 or Mozilla 1.x"> -<!ENTITY importFromSeamonkey.accesskey "N"> <!ENTITY importFromOpera.label "Opera"> <!ENTITY importFromOpera.accesskey "O"> <!ENTITY importFromSafari.label "Safari"> <!ENTITY importFromSafari.accesskey "S"> <!ENTITY importFromChrome.label "Chrome"> <!ENTITY importFromChrome.accesskey "C"> <!ENTITY importFromHTMLFile.label "From an HTML File"> <!ENTITY importFromHTMLFile.accesskey "F">
--- a/browser/locales/en-US/chrome/browser/migration/migration.properties +++ b/browser/locales/en-US/chrome/browser/migration/migration.properties @@ -1,62 +1,53 @@ profileName_format=%S %S # Browser Specific sourceNameIE=Internet Explorer -sourceNameSeamonkey=Netscape 6/7/Mozilla sourceNameOpera=Opera sourceNameSafari=Safari sourceNameChrome=Google Chrome importedBookmarksFolder=From %S importedSearchURLsFolder=Keyword Searches (From %S) importedSearchURLsTitle=Search on %S importedSearchUrlDesc=Type "%S <search query>" in the Location Bar to perform a search on %S. -importedSeamonkeyBookmarksTitle=From Netscape 6/7/Mozilla importedSafariBookmarks=From Safari importedOperaHotlistTitle=From Opera importedOperaSearchUrls=Keyword Searches (From Opera) # Import Sources 1_ie=Internet Options 1_opera=Preferences -1_seamonkey=Preferences 1_safari=Preferences 1_chrome=Preferences 2_ie=Cookies 2_opera=Cookies -2_seamonkey=Cookies 2_safari=Cookies 2_chrome=Cookies 4_ie=Browsing History 4_opera=Browsing History -4_seamonkey=Browsing History 4_safari=Browsing History 4_chrome=Browsing History 8_ie=Saved Form History 8_opera=Saved Form History -8_seamonkey=Saved Form History 8_safari=Saved Form History 8_chrome=Saved Form History 16_ie=Saved Passwords 16_opera=Saved Passwords -16_seamonkey=Saved Passwords 16_safari=Saved Passwords 16_chrome=Saved Passwords 32_ie=Favorites 32_opera=Bookmarks -32_seamonkey=Bookmarks 32_safari=Bookmarks 32_chrome=Bookmarks 64_ie=Other Data 64_opera=Other Data -64_seamonkey=Other Data 64_safari=Other Data 64_chrome=Other Data
--- a/browser/locales/en-US/chrome/browser/tabview.properties +++ b/browser/locales/en-US/chrome/browser/tabview.properties @@ -1,4 +1,8 @@ -tabview.groupItem.defaultName=Name this tab group… +tabview.button.searchTabs=Search tab groups +tabview.button.exitTabGroups=Exit tab groups +tabview.groupItem.defaultName=Name this tab group +tabview.groupItem.closeGroup=Close group tabview.groupItem.undoCloseGroup=Undo Close Group +tabview.groupItem.discardClosedGroup=Discard closed group tabview.search.otherWindowTabs=Tabs from other windows tabview.notification.sessionStore=Tabs and groups will automatically be restored the next time you start %S.
--- a/configure.in +++ b/configure.in @@ -4359,17 +4359,17 @@ dnl = If NSS was not detected in the sys dnl = use the one in the source tree (mozilla/security/nss) dnl ======================================================== MOZ_ARG_WITH_BOOL(system-nss, [ --with-system-nss Use system installed NSS], _USE_SYSTEM_NSS=1 ) if test -n "$_USE_SYSTEM_NSS"; then - AM_PATH_NSS(3.13.1, [MOZ_NATIVE_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])]) + AM_PATH_NSS(3.13.2, [MOZ_NATIVE_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])]) fi if test -n "$MOZ_NATIVE_NSS"; then NSS_LIBS="$NSS_LIBS -lcrmf" else NSS_CFLAGS='-I$(LIBXUL_DIST)/include/nss' NSS_DEP_LIBS="\ \$(LIBXUL_DIST)/lib/\$(LIB_PREFIX)crmf.\$(LIB_SUFFIX) \
--- a/content/canvas/src/WebGLContext.cpp +++ b/content/canvas/src/WebGLContext.cpp @@ -1157,19 +1157,31 @@ WebGLContext::Notify(nsITimer* timer) } void WebGLContext::MaybeRestoreContext() { if (mContextLost || mAllowRestore) return; - gl->MakeCurrent(); - GLContext::ContextResetARB resetStatus = - (GLContext::ContextResetARB) gl->fGetGraphicsResetStatus(); + GLContext::ContextResetARB resetStatus = GLContext::CONTEXT_NO_ERROR; + if (mHasRobustness) { + gl->MakeCurrent(); + resetStatus = (GLContext::ContextResetARB) gl->fGetGraphicsResetStatus(); + // This call is safe as it does not actually interact with GL, so the + // context does not have to be current. + } else if (gl->GetContextType() == GLContext::ContextTypeEGL) { + // Simulate a ARB_robustness guilty context loss for when we + // get an EGL_CONTEXT_LOST error. It may not actually be guilty, + // but we can't make any distinction, so we must assume the worst + // case. + if (!gl->MakeCurrent(true) && gl->IsContextLost()) { + resetStatus = GLContext::CONTEXT_GUILTY_CONTEXT_RESET_ARB; + } + } if (resetStatus != GLContext::CONTEXT_NO_ERROR) { // It's already lost, but clean up after it and signal to JS that it is // lost. ForceLoseContext(); } switch (resetStatus) {
--- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -52,16 +52,17 @@ #include "nsIDOMWebGLRenderingContext.h" #include "nsICanvasRenderingContextInternal.h" #include "nsHTMLCanvasElement.h" #include "nsWeakReference.h" #include "nsIDOMHTMLElement.h" #include "nsIJSNativeInitializer.h" #include "nsIMemoryReporter.h" +#include "nsContentUtils.h" #include "GLContextProvider.h" #include "Layers.h" #include "CheckedInt.h" /* * Minimum value constants defined in 6.2 State Tables of OpenGL ES - 2.0.25 @@ -444,17 +445,17 @@ public: bool MinCapabilityMode() const { return mMinCapability; } // Sets up the GL_ARB_robustness timer if it isn't already, so that if the // driver gets restarted, the context may get reset with it. void SetupRobustnessTimer() { - if (mContextLost || !mHasRobustness) + if (mContextLost || (!mHasRobustness && gl->GetContextType() != gl::GLContext::ContextTypeEGL)) return; // If the timer was already running, don't restart it here. Instead, // wait until the previous call is done, then fire it one more time. // This is an optimization to prevent unnecessary cross-communication // between threads. if (mRobustnessTimerRunning) { mDrawSinceRobustnessTimerSet = true;
--- a/content/html/document/src/VideoDocument.cpp +++ b/content/html/document/src/VideoDocument.cpp @@ -79,18 +79,16 @@ VideoDocument::StartDocumentLoad(const c nsIContentSink* aSink) { nsresult rv = MediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer, aDocListener, aReset, aSink); NS_ENSURE_SUCCESS(rv, rv); mStreamListener = new MediaDocumentStreamListener(this); - if (!mStreamListener) - return NS_ERROR_OUT_OF_MEMORY; // Create synthetic document rv = CreateSyntheticVideoDocument(aChannel, getter_AddRefs(mStreamListener->mNextStream)); NS_ENSURE_SUCCESS(rv, rv); NS_ADDREF(*aDocListener = mStreamListener); return rv; @@ -153,17 +151,16 @@ VideoDocument::UpdateTitle(nsIChannel* a } // namespace dom } // namespace mozilla nsresult NS_NewVideoDocument(nsIDocument** aResult) { mozilla::dom::VideoDocument* doc = new mozilla::dom::VideoDocument(); - NS_ENSURE_TRUE(doc, NS_ERROR_OUT_OF_MEMORY); NS_ADDREF(doc); nsresult rv = doc->Init(); if (NS_FAILED(rv)) { NS_RELEASE(doc); }
--- a/content/html/document/src/nsHTMLDocument.cpp +++ b/content/html/document/src/nsHTMLDocument.cpp @@ -1740,17 +1740,18 @@ nsHTMLDocument::Open(const nsAString& aC // Now make sure we're not flagged as the initial document anymore, now // that we've had stuff done to us. From now on, if anyone tries to // document.open() us, they get a new inner window. SetIsInitialDocument(false); nsCOMPtr<nsIScriptGlobalObject> newScope(do_QueryReferent(mScopeObject)); if (oldScope && newScope != oldScope) { - nsContentUtils::ReparentContentWrappersInScope(cx, oldScope, newScope); + rv = nsContentUtils::ReparentContentWrappersInScope(cx, oldScope, newScope); + NS_ENSURE_SUCCESS(rv, rv); } } // Call Reset(), this will now do the full reset Reset(channel, group); if (baseURI) { mDocumentBaseURI = baseURI; } @@ -1949,16 +1950,18 @@ nsHTMLDocument::WriteCommon(JSContext *c 1, getter_AddRefs(ignored)); // If Open() fails, or if it didn't create a parser (as it won't // if the user chose to not discard the current document through // onbeforeunload), don't write anything. if (NS_FAILED(rv) || !mParser) { return rv; } + NS_ABORT_IF_FALSE(!JS_IsExceptionPending(cx), + "Open() succeeded but JS exception is pending"); } static NS_NAMED_LITERAL_STRING(new_line, "\n"); // Save the data in cache if (mWyciwygChannel) { if (!aText.IsEmpty()) { mWyciwygChannel->WriteToCacheEntry(aText);
--- a/content/media/nsMediaCache.cpp +++ b/content/media/nsMediaCache.cpp @@ -1072,17 +1072,17 @@ nsMediaCache::PredictNextUseForIncomingD } if (bytesAhead <= 0) return TimeDuration(0); PRInt64 millisecondsAhead = bytesAhead*1000/aStream->mPlaybackBytesPerSecond; return TimeDuration::FromMilliseconds( NS_MIN<PRInt64>(millisecondsAhead, PR_INT32_MAX)); } -enum StreamAction { NONE, SEEK, RESUME, SUSPEND }; +enum StreamAction { NONE, SEEK, SEEK_AND_RESUME, RESUME, SUSPEND }; void nsMediaCache::Update() { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); // The action to use for each stream. We store these so we can make // decisions while holding the cache lock but implement those decisions @@ -1317,66 +1317,83 @@ nsMediaCache::Update() if (stream->mChannelOffset != desiredOffset && enableReading) { // We need to seek now. NS_ASSERTION(stream->mIsSeekable || desiredOffset == 0, "Trying to seek in a non-seekable stream!"); // Round seek offset down to the start of the block. This is essential // because we don't want to think we have part of a block already // in mPartialBlockBuffer. stream->mChannelOffset = (desiredOffset/BLOCK_SIZE)*BLOCK_SIZE; - actions[i] = SEEK; + actions[i] = stream->mCacheSuspended ? SEEK_AND_RESUME : SEEK; } else if (enableReading && stream->mCacheSuspended) { actions[i] = RESUME; } else if (!enableReading && !stream->mCacheSuspended) { actions[i] = SUSPEND; } } #ifdef DEBUG mInUpdate = false; #endif } // Update the channel state without holding our cache lock. While we're // doing this, decoder threads may be running and seeking, reading or changing // other cache state. That's OK, they'll trigger new Update events and we'll // get back here and revise our decisions. The important thing here is that // performing these actions only depends on mChannelOffset and - // mCacheSuspended, which can only be written by the main thread (i.e., this + // the action, which can only be written by the main thread (i.e., this // thread), so we don't have races here. + + // First, update the mCacheSuspended/mCacheEnded flags so that they're all correct + // when we fire our CacheClient commands below. Those commands can rely on these flags + // being set correctly for all streams. for (PRUint32 i = 0; i < mStreams.Length(); ++i) { nsMediaCacheStream* stream = mStreams[i]; - nsresult rv = NS_OK; switch (actions[i]) { case SEEK: - LOG(PR_LOG_DEBUG, ("Stream %p CacheSeek to %lld (resume=%d)", stream, - (long long)stream->mChannelOffset, stream->mCacheSuspended)); - rv = stream->mClient->CacheClientSeek(stream->mChannelOffset, - stream->mCacheSuspended); + case SEEK_AND_RESUME: stream->mCacheSuspended = false; stream->mChannelEnded = false; break; + case RESUME: + stream->mCacheSuspended = false; + break; + case SUSPEND: + stream->mCacheSuspended = true; + break; + default: + break; + } + stream->mHasHadUpdate = true; + } + for (PRUint32 i = 0; i < mStreams.Length(); ++i) { + nsMediaCacheStream* stream = mStreams[i]; + nsresult rv; + switch (actions[i]) { + case SEEK: + case SEEK_AND_RESUME: + LOG(PR_LOG_DEBUG, ("Stream %p CacheSeek to %lld (resume=%d)", stream, + (long long)stream->mChannelOffset, actions[i] == SEEK_AND_RESUME)); + rv = stream->mClient->CacheClientSeek(stream->mChannelOffset, + actions[i] == SEEK_AND_RESUME); + break; case RESUME: LOG(PR_LOG_DEBUG, ("Stream %p Resumed", stream)); rv = stream->mClient->CacheClientResume(); - stream->mCacheSuspended = false; break; - case SUSPEND: LOG(PR_LOG_DEBUG, ("Stream %p Suspended", stream)); rv = stream->mClient->CacheClientSuspend(); - stream->mCacheSuspended = true; break; - default: + rv = NS_OK; break; } - stream->mHasHadUpdate = true; - if (NS_FAILED(rv)) { // Close the streams that failed due to error. This will cause all // client Read and Seek operations on those streams to fail. Blocked // Reads will also be woken up. ReentrantMonitorAutoEnter mon(mReentrantMonitor); stream->CloseInternal(mon); } }
--- a/content/media/nsMediaStream.cpp +++ b/content/media/nsMediaStream.cpp @@ -761,17 +761,17 @@ nsMediaChannelStream::CacheClientNotifyD NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); } nsresult nsMediaChannelStream::CacheClientSeek(PRInt64 aOffset, bool aResume) { NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); - printf("*** nsMediaChannelStream::CacheClientSeek() mDecoder=%p\n", mDecoder); + printf("*** nsMediaChannelStream::CacheClientSeek() mDecoder=%p aOffset=%lld aResume = %d\n", mDecoder, (long long)aOffset, aResume); CloseChannel(); if (aResume) { NS_ASSERTION(mSuspendCount > 0, "Too many resumes!"); // No need to mess with the channel, since we're making a new one --mSuspendCount; }
--- a/dbm/src/Makefile.in +++ b/dbm/src/Makefile.in @@ -74,16 +74,17 @@ endif ifeq (,$(filter -DHAVE_SNPRINTF=1,$(ACDEFINES))) CSRCS += snprintf.c endif endif # WINNT LOCAL_INCLUDES = -I$(srcdir)/../include FORCE_STATIC_LIB = 1 +FORCE_USE_PIC = 1 include $(topsrcdir)/config/rules.mk DEFINES += -DMEMMOVE -D__DBINTERFACE_PRIVATE $(SECURITY_FLAG) ifeq ($(OS_ARCH),WINCE) DEFINES += -D__STDC__ -DDBM_REOPEN_ON_FLUSH endif
new file mode 100644 --- /dev/null +++ b/dom/base/crashtests/706283-1.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> + +window.mozRequestAnimationFrame(null); + +</script>
--- a/dom/base/crashtests/crashtests.list +++ b/dom/base/crashtests/crashtests.list @@ -27,8 +27,9 @@ load 601247.html load 609560-1.xhtml load 612018-1.html load 637116.html load 666869.html load 675621-1.html load 693894.html load 695867.html load 697643.html +load 706283-1.html
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -1322,16 +1322,17 @@ nsDOMWindowUtils::SendQueryContentEvent( nsQueryContentEvent dummyEvent(true, NS_QUERY_CONTENT_STATE, widget); InitEvent(dummyEvent, &pt); nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates(presContext->GetRootPresContext(), &dummyEvent); nsIntRect widgetBounds; nsresult rv = widget->GetClientBounds(widgetBounds); NS_ENSURE_SUCCESS(rv, rv); + widgetBounds.MoveTo(0, 0); // There is no popup frame at the point and the point isn't in our widget, // we cannot process this request. NS_ENSURE_TRUE(popupFrame || widgetBounds.Contains(pt), NS_ERROR_FAILURE); // Fire the event on the widget at the point if (popupFrame) {
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -3878,16 +3878,20 @@ nsGlobalWindow::MozRequestAnimationFrame { FORWARD_TO_INNER(MozRequestAnimationFrame, (aCallback), NS_ERROR_NOT_INITIALIZED); if (!mDoc) { return NS_OK; } + if (!aCallback) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + mDoc->ScheduleFrameRequestCallback(aCallback); return NS_OK; } NS_IMETHODIMP nsGlobalWindow::GetMozAnimationStartTime(PRInt64 *aTime) { FORWARD_TO_INNER(GetMozAnimationStartTime, (aTime), NS_ERROR_NOT_INITIALIZED); @@ -10315,17 +10319,17 @@ nsGlobalChromeWindow::BeginWindowMove(ns #ifdef MOZ_XUL if (aPanel) { nsCOMPtr<nsIContent> panel = do_QueryInterface(aPanel); NS_ENSURE_TRUE(panel, NS_ERROR_FAILURE); nsIFrame* frame = panel->GetPrimaryFrame(); NS_ENSURE_TRUE(frame && frame->GetType() == nsGkAtoms::menuPopupFrame, NS_OK); - (static_cast<nsMenuPopupFrame*>(frame))->GetWidget(getter_AddRefs(widget)); + widget = (static_cast<nsMenuPopupFrame*>(frame))->GetWidget(); } else { #endif widget = GetMainWidget(); #ifdef MOZ_XUL } #endif
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -220,18 +220,17 @@ ContentParent::Init() } nsCOMPtr<nsIPrefBranch2> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefs) { prefs->AddObserver("", this, false); } nsCOMPtr<nsIThreadInternal> threadInt(do_QueryInterface(NS_GetCurrentThread())); if (threadInt) { - threadInt->GetObserver(getter_AddRefs(mOldObserver)); - threadInt->SetObserver(this); + threadInt->AddObserver(this); } if (obs) { obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-created", nsnull); } #ifdef ACCESSIBILITY // If accessibility is running in chrome process then start it in content // process. @@ -339,17 +338,17 @@ ContentParent::ActorDestroy(ActorDestroy } RecvRemoveGeolocationListener(); RecvRemoveDeviceMotionListener(); nsCOMPtr<nsIThreadInternal> threadInt(do_QueryInterface(NS_GetCurrentThread())); if (threadInt) - threadInt->SetObserver(mOldObserver); + threadInt->RemoveObserver(this); if (mRunToCompletionDepth) mRunToCompletionDepth = 0; if (gContentParents) { gContentParents->RemoveElement(this); if (!gContentParents->Length()) { delete gContentParents; gContentParents = NULL; @@ -1113,35 +1112,30 @@ ContentParent::RecvLoadURIExternal(const nsCOMPtr<nsIURI> ourURI(uri); extProtService->LoadURI(ourURI, nsnull); return true; } /* void onDispatchedEvent (in nsIThreadInternal thread); */ NS_IMETHODIMP ContentParent::OnDispatchedEvent(nsIThreadInternal *thread) -{ - if (mOldObserver) - return mOldObserver->OnDispatchedEvent(thread); - - return NS_OK; +{ + NS_NOTREACHED("OnDispatchedEvent unimplemented"); + return NS_ERROR_NOT_IMPLEMENTED; } /* void onProcessNextEvent (in nsIThreadInternal thread, in boolean mayWait, in unsigned long recursionDepth); */ NS_IMETHODIMP ContentParent::OnProcessNextEvent(nsIThreadInternal *thread, bool mayWait, PRUint32 recursionDepth) { if (mRunToCompletionDepth) ++mRunToCompletionDepth; - if (mOldObserver) - return mOldObserver->OnProcessNextEvent(thread, mayWait, recursionDepth); - return NS_OK; } /* void afterProcessNextEvent (in nsIThreadInternal thread, in unsigned long recursionDepth); */ NS_IMETHODIMP ContentParent::AfterProcessNextEvent(nsIThreadInternal *thread, PRUint32 recursionDepth) { @@ -1151,19 +1145,16 @@ ContentParent::AfterProcessNextEvent(nsI printf("... ran to completion.\n"); #endif if (mShouldCallUnblockChild) { mShouldCallUnblockChild = false; UnblockChild(); } } - if (mOldObserver) - return mOldObserver->AfterProcessNextEvent(thread, recursionDepth); - return NS_OK; } bool ContentParent::RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, const nsString& aText, const bool& aTextClickable, const nsString& aCookie, const nsString& aName) {
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -230,17 +230,16 @@ private: const PRUint32& aFlags, const nsCString& aCategory); GeckoChildProcessHost* mSubprocess; PRInt32 mGeolocationWatchID; int mRunToCompletionDepth; bool mShouldCallUnblockChild; - nsCOMPtr<nsIThreadObserver> mOldObserver; // This is a cache of all of the memory reporters // registered in the child process. To update this, one // can broadcast the topic "child-memory-reporter-request" using // the nsIObserverService. nsCOMArray<nsIMemoryReporter> mMemoryReporters; bool mIsAlive;
--- a/dom/src/storage/nsDOMStorage.cpp +++ b/dom/src/storage/nsDOMStorage.cpp @@ -68,16 +68,17 @@ using mozilla::dom::StorageChild; #include "nsCycleCollectionParticipant.h" #include "nsIOfflineCacheUpdate.h" #include "nsIJSContextStack.h" #include "nsIPrivateBrowsingService.h" #include "nsDOMString.h" #include "nsNetCID.h" #include "mozilla/Preferences.h" #include "nsThreadUtils.h" +#include "mozilla/Telemetry.h" // calls FlushAndDeleteTemporaryTables(false) #define NS_DOMSTORAGE_FLUSH_TIMER_TOPIC "domstorage-flush-timer" // calls FlushAndDeleteTemporaryTables(true) #define NS_DOMSTORAGE_FLUSH_FORCE_TOPIC "domstorage-flush-force" using namespace mozilla; @@ -1596,32 +1597,69 @@ nsDOMStorage::GetItem(const nsAString& a NS_ENSURE_SUCCESS(rv, rv); } else SetDOMStringToNull(aData); return NS_OK; } +static Telemetry::ID +TelemetryIDForKey(nsPIDOMStorage::nsDOMStorageType type) +{ + switch (type) { + default: + MOZ_ASSERT(false); + // We need to return something to satisfy the compiler. + // Fallthrough. + case nsPIDOMStorage::GlobalStorage: + return Telemetry::GLOBALDOMSTORAGE_KEY_SIZE_BYTES; + case nsPIDOMStorage::LocalStorage: + return Telemetry::LOCALDOMSTORAGE_KEY_SIZE_BYTES; + case nsPIDOMStorage::SessionStorage: + return Telemetry::SESSIONDOMSTORAGE_KEY_SIZE_BYTES; + } +} + +static Telemetry::ID +TelemetryIDForValue(nsPIDOMStorage::nsDOMStorageType type) +{ + switch (type) { + default: + MOZ_ASSERT(false); + // We need to return something to satisfy the compiler. + // Fallthrough. + case nsPIDOMStorage::GlobalStorage: + return Telemetry::GLOBALDOMSTORAGE_VALUE_SIZE_BYTES; + case nsPIDOMStorage::LocalStorage: + return Telemetry::LOCALDOMSTORAGE_VALUE_SIZE_BYTES; + case nsPIDOMStorage::SessionStorage: + return Telemetry::SESSIONDOMSTORAGE_VALUE_SIZE_BYTES; + } +} + NS_IMETHODIMP nsDOMStorage::GetItem(const nsAString& aKey, nsIDOMStorageItem **aItem) { nsresult rv; NS_IF_ADDREF(*aItem = GetNamedItem(aKey, &rv)); return rv; } NS_IMETHODIMP nsDOMStorage::SetItem(const nsAString& aKey, const nsAString& aData) { if (!CacheStoragePermissions()) return NS_ERROR_DOM_SECURITY_ERR; + Telemetry::Accumulate(TelemetryIDForKey(mStorageType), aKey.Length()); + Telemetry::Accumulate(TelemetryIDForValue(mStorageType), aData.Length()); + nsString oldValue; nsresult rv = mStorageImpl->SetValue(IsCallerSecure(), aKey, aData, oldValue); if (NS_FAILED(rv)) return rv; if ((oldValue != aData || mStorageType == GlobalStorage) && mEventBroadcaster) mEventBroadcaster->BroadcastChangeNotification(aKey, oldValue, aData);
--- a/dom/tests/unit/test_domstorage_aboutpages.js +++ b/dom/tests/unit/test_domstorage_aboutpages.js @@ -8,25 +8,44 @@ Components.utils.import("resource://gre/ function run_test() { // Needs a profile folder for the database. do_get_profile(); testURI(Services.io.newURI("about:mozilla", null, null)); testURI(Services.io.newURI("moz-safe-about:rights", null, null)); } +function sum(a) +{ + return a.reduce(function(prev, current, index, array) { + return prev + current; + }); +} + function testURI(aURI) { print("Testing: " + aURI.spec); let storage = getStorageForURI(aURI); + let Telemetry = Components.classes["@mozilla.org/base/telemetry;1"]. + getService(Components.interfaces.nsITelemetry); + let key_histogram = Telemetry.getHistogramById("LOCALDOMSTORAGE_KEY_SIZE_BYTES"); + let value_histogram = Telemetry.getHistogramById("LOCALDOMSTORAGE_VALUE_SIZE_BYTES"); + let before_key_snapshot = key_histogram.snapshot(); + let before_value_snapshot = value_histogram.snapshot(); storage.setItem("test-item", "test-value"); print("Check that our value has been correctly stored."); + let after_key_snapshot = key_histogram.snapshot(); + let after_value_snapshot = value_histogram.snapshot(); do_check_eq(storage.length, 1); do_check_eq(storage.key(0), "test-item"); do_check_eq(storage.getItem("test-item"), "test-value"); + do_check_eq(sum(after_key_snapshot.counts), + sum(before_key_snapshot.counts)+1); + do_check_eq(sum(after_value_snapshot.counts), + sum(before_value_snapshot.counts)+1); print("Check that our value is correctly removed."); storage.removeItem("test-item"); do_check_eq(storage.length, 0); do_check_eq(storage.getItem("test-item"), null); testURIWithPrivateBrowsing(aURI);
--- a/dom/workers/EventTarget.cpp +++ b/dom/workers/EventTarget.cpp @@ -134,16 +134,19 @@ EventTarget::FromJSObject(JSContext* aCx return GetPrivate(aCx, aObj); } // static JSBool EventTarget::AddEventListener(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return true; + } EventTarget* self = GetPrivate(aCx, obj); if (!self) { return true; } JSString* type; JSObject* listener; @@ -162,16 +165,19 @@ EventTarget::AddEventListener(JSContext* capturing, wantsUntrusted); } // static JSBool EventTarget::RemoveEventListener(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return true; + } EventTarget* self = GetPrivate(aCx, obj); if (!self) { return true; } JSString* type; JSObject* listener; @@ -190,16 +196,19 @@ EventTarget::RemoveEventListener(JSConte capturing); } // static JSBool EventTarget::DispatchEvent(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return true; + } EventTarget* self = GetPrivate(aCx, obj); if (!self) { return true; } JSObject* event; if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "o", &event)) {
--- a/dom/workers/Events.cpp +++ b/dom/workers/Events.cpp @@ -312,46 +312,55 @@ private: *aVp = INT_TO_JSVAL(JSID_TO_INT(idval)); return true; } static JSBool StopPropagation(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } Event* event = GetInstancePrivate(aCx, obj, sFunctions[0].name); if (!event) { return false; } event->mStopPropagationCalled = true; return true; } static JSBool StopImmediatePropagation(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } Event* event = GetInstancePrivate(aCx, obj, sFunctions[3].name); if (!event) { return false; } event->mStopImmediatePropagationCalled = true; return true; } static JSBool PreventDefault(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } Event* event = GetInstancePrivate(aCx, obj, sFunctions[1].name); if (!event) { return false; } jsval cancelableVal; if (!GetPropertyCommon(aCx, obj, SLOT_cancelable, &cancelableVal)) { @@ -362,16 +371,19 @@ private: JS_SetReservedSlot(aCx, obj, SLOT_defaultPrevented, cancelableVal) : true; } static JSBool InitEvent(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } Event* event = GetInstancePrivate(aCx, obj, sFunctions[2].name); if (!event) { return false; } JSString* type; JSBool bubbles, cancelable; @@ -622,16 +634,19 @@ private: return JS_GetReservedSlot(aCx, aObj, slot, aVp); } static JSBool InitMessageEvent(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } MessageEvent* event = GetInstancePrivate(aCx, obj, sFunctions[0].name); if (!event) { return false; } JSString* type, *data, *origin; JSBool bubbles, cancelable; @@ -818,16 +833,19 @@ private: return JS_GetReservedSlot(aCx, aObj, slot, aVp); } static JSBool InitErrorEvent(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } ErrorEvent* event = GetInstancePrivate(aCx, obj, sFunctions[0].name); if (!event) { return false; } JSString* type, *message, *filename; JSBool bubbles, cancelable; @@ -1005,16 +1023,19 @@ private: return JS_GetReservedSlot(aCx, aObj, slot, aVp); } static JSBool InitProgressEvent(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } ProgressEvent* event = GetInstancePrivate(aCx, obj, sFunctions[0].name); if (!event) { return false; } JSString* type; JSBool bubbles, cancelable, lengthComputable;
--- a/dom/workers/Exceptions.cpp +++ b/dom/workers/Exceptions.cpp @@ -117,16 +117,19 @@ private: JS_ASSERT(JS_GET_CLASS(aCx, aObj) == &sClass); delete GetJSPrivateSafeish<DOMException>(aCx, aObj); } static JSBool ToString(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } JSClass* classPtr; if (!obj || ((classPtr = JS_GET_CLASS(aCx, obj)) != &sClass)) { JS_ReportErrorNumber(aCx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO, sClass.name, "toString", classPtr ? classPtr->name : "object"); return false; }
--- a/dom/workers/File.cpp +++ b/dom/workers/File.cpp @@ -180,16 +180,19 @@ private: return true; } static JSBool MozSlice(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } nsIDOMBlob* blob = GetInstancePrivate(aCx, obj, "mozSlice"); if (!blob) { return false; } jsdouble start = 0, end = 0; JSString* jsContentType = JS_GetEmptyString(JS_GetRuntime(aCx));
--- a/dom/workers/FileReaderSync.cpp +++ b/dom/workers/FileReaderSync.cpp @@ -175,16 +175,19 @@ private: GetJSPrivateSafeish<FileReaderSyncPrivate>(aCx, aObj); NS_IF_RELEASE(fileReader); } static JSBool ReadAsArrayBuffer(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } FileReaderSyncPrivate* fileReader = GetInstancePrivate(aCx, obj, "readAsArrayBuffer"); if (!fileReader) { return false; } JSObject* jsBlob; @@ -219,16 +222,19 @@ private: JS_SET_RVAL(aCx, aVp, OBJECT_TO_JSVAL(jsArrayBuffer)); return true; } static JSBool ReadAsDataURL(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } FileReaderSyncPrivate* fileReader = GetInstancePrivate(aCx, obj, "readAsDataURL"); if (!fileReader) { return false; } JSObject* jsBlob; @@ -256,16 +262,19 @@ private: JS_SET_RVAL(aCx, aVp, STRING_TO_JSVAL(jsBlobText)); return true; } static JSBool ReadAsBinaryString(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } FileReaderSyncPrivate* fileReader = GetInstancePrivate(aCx, obj, "readAsBinaryString"); if (!fileReader) { return false; } JSObject* jsBlob; @@ -293,16 +302,19 @@ private: JS_SET_RVAL(aCx, aVp, STRING_TO_JSVAL(jsBlobText)); return true; } static JSBool ReadAsText(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } FileReaderSyncPrivate* fileReader = GetInstancePrivate(aCx, obj, "readAsText"); if (!fileReader) { return false; } JSObject* jsBlob;
--- a/dom/workers/Worker.cpp +++ b/dom/workers/Worker.cpp @@ -258,30 +258,36 @@ private: worker->TraceInstance(aTrc); } } static JSBool Terminate(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } const char*& name = sFunctions[0].name; WorkerPrivate* worker = GetInstancePrivate(aCx, obj, name); if (!worker) { return !JS_IsExceptionPending(aCx); } return worker->Terminate(aCx); } static JSBool PostMessage(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } const char*& name = sFunctions[1].name; WorkerPrivate* worker = GetInstancePrivate(aCx, obj, name); if (!worker) { return !JS_IsExceptionPending(aCx); } jsval message;
--- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -373,29 +373,35 @@ private: *aVp = scope->mSlots[SLOT_navigator]; return true; } static JSBool Close(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } WorkerGlobalScope* scope = GetInstancePrivate(aCx, obj, sFunctions[0].name); if (!scope) { return false; } return scope->mWorker->CloseInternal(aCx); } static JSBool ImportScripts(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } WorkerGlobalScope* scope = GetInstancePrivate(aCx, obj, sFunctions[1].name); if (!scope) { return false; } if (aArgc && !scriptloader::Load(aCx, aArgc, JS_ARGV(aCx, aVp))) { return false; @@ -403,16 +409,19 @@ private: return true; } static JSBool SetTimeout(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } WorkerGlobalScope* scope = GetInstancePrivate(aCx, obj, sFunctions[2].name); if (!scope) { return false; } jsval dummy; if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", &dummy)) { @@ -421,16 +430,19 @@ private: return scope->mWorker->SetTimeout(aCx, aArgc, aVp, false); } static JSBool ClearTimeout(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } WorkerGlobalScope* scope = GetInstancePrivate(aCx, obj, sFunctions[3].name); if (!scope) { return false; } uint32 id; if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "u", &id)) { @@ -439,16 +451,19 @@ private: return scope->mWorker->ClearTimeout(aCx, id); } static JSBool SetInterval(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } WorkerGlobalScope* scope = GetInstancePrivate(aCx, obj, sFunctions[4].name); if (!scope) { return false; } jsval dummy; if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", &dummy)) { @@ -457,16 +472,19 @@ private: return scope->mWorker->SetTimeout(aCx, aArgc, aVp, true); } static JSBool ClearInterval(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } WorkerGlobalScope* scope = GetInstancePrivate(aCx, obj, sFunctions[5].name); if (!scope) { return false; } uint32 id; if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "u", &id)) { @@ -474,18 +492,22 @@ private: } return scope->mWorker->ClearTimeout(aCx, id); } static JSBool Dump(JSContext* aCx, uintN aArgc, jsval* aVp) { - if (!GetInstancePrivate(aCx, JS_THIS_OBJECT(aCx, aVp), - sFunctions[6].name)) { + JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } + + if (!GetInstancePrivate(aCx, obj, sFunctions[6].name)) { return false; } if (aArgc) { JSString* str = JS_ValueToString(aCx, JS_ARGV(aCx, aVp)[0]); if (!str) { return false; } @@ -500,18 +522,22 @@ private: } return true; } static JSBool AtoB(JSContext* aCx, uintN aArgc, jsval* aVp) { - if (!GetInstancePrivate(aCx, JS_THIS_OBJECT(aCx, aVp), - sFunctions[7].name)) { + JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } + + if (!GetInstancePrivate(aCx, obj, sFunctions[7].name)) { return false; } jsval string; if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", &string)) { return false; } @@ -522,18 +548,22 @@ private: JS_SET_RVAL(aCx, aVp, result); return true; } static JSBool BtoA(JSContext* aCx, uintN aArgc, jsval* aVp) { - if (!GetInstancePrivate(aCx, JS_THIS_OBJECT(aCx, aVp), - sFunctions[8].name)) { + JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } + + if (!GetInstancePrivate(aCx, obj, sFunctions[8].name)) { return false; } jsval binary; if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", &binary)) { return false; } @@ -741,16 +771,19 @@ private: scope->TraceInstance(aTrc); } } static JSBool PostMessage(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } const char*& name = sFunctions[0].name; DedicatedWorkerGlobalScope* scope = GetInstancePrivate(aCx, obj, name); if (!scope) { return false; } jsval message;
--- a/dom/workers/XMLHttpRequest.cpp +++ b/dom/workers/XMLHttpRequest.cpp @@ -600,30 +600,36 @@ private: return priv->SetEventListenerOnEventTarget(aCx, name + 2, aVp); } static JSBool Abort(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } XMLHttpRequestPrivate* priv = GetInstancePrivate(aCx, obj, sFunctions[0].name); if (!priv) { return false; } return priv->Abort(aCx); } static JSBool GetAllResponseHeaders(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } XMLHttpRequestPrivate* priv = GetInstancePrivate(aCx, obj, sFunctions[1].name); if (!priv) { return false; } JSString* responseHeaders = priv->GetAllResponseHeaders(aCx); @@ -634,16 +640,19 @@ private: JS_SET_RVAL(aCx, aVp, STRING_TO_JSVAL(responseHeaders)); return true; } static JSBool GetResponseHeader(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } XMLHttpRequestPrivate* priv = GetInstancePrivate(aCx, obj, sFunctions[2].name); if (!priv) { return false; } jsval headerVal; @@ -670,16 +679,19 @@ private: JS_SET_RVAL(aCx, aVp, STRING_TO_JSVAL(value)); return true; } static JSBool Open(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } XMLHttpRequestPrivate* priv = GetInstancePrivate(aCx, obj, sFunctions[3].name); if (!priv) { return false; } JSString* method, *url; @@ -693,32 +705,38 @@ private: return priv->Open(aCx, method, url, async, user, password); } static JSBool Send(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } XMLHttpRequestPrivate* priv = GetInstancePrivate(aCx, obj, sFunctions[4].name); if (!priv) { return false; } jsval body = aArgc ? JS_ARGV(aCx, aVp)[0] : JSVAL_VOID; return priv->Send(aCx, !!aArgc, body); } static JSBool SendAsBinary(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } XMLHttpRequestPrivate* priv = GetInstancePrivate(aCx, obj, sFunctions[5].name); if (!priv) { return false; } jsval bodyVal; @@ -739,16 +757,19 @@ private: return priv->SendAsBinary(aCx, body); } static JSBool SetRequestHeader(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } XMLHttpRequestPrivate* priv = GetInstancePrivate(aCx, obj, sFunctions[6].name); if (!priv) { return false; } JSString* header, *value; @@ -759,16 +780,19 @@ private: return priv->SetRequestHeader(aCx, header, value); } static JSBool OverrideMimeType(JSContext* aCx, uintN aArgc, jsval* aVp) { JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + if (!obj) { + return false; + } XMLHttpRequestPrivate* priv = GetInstancePrivate(aCx, obj, sFunctions[7].name); if (!priv) { return false; } JSString* mimeType;
--- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -2493,24 +2493,26 @@ GLContext::SetBlitFramebufferForDestText fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mBlitFramebuffer); fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, aTexture, 0); - if (aTexture && (fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) != - LOCAL_GL_FRAMEBUFFER_COMPLETE)) { - + GLenum result = fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (aTexture && (result != LOCAL_GL_FRAMEBUFFER_COMPLETE)) { + nsCAutoString msg; + msg.Append("Framebuffer not complete -- error 0x"); + msg.AppendInt(result, 16); // Note: if you are hitting this, it is likely that // your texture is not texture complete -- that is, you // allocated a texture name, but didn't actually define its // size via a call to TexImage2D. - NS_RUNTIMEABORT("Error setting up framebuffer --- framebuffer not complete!"); + NS_RUNTIMEABORT(msg.get()); } } #ifdef DEBUG void GLContext::CreatedProgram(GLContext *aOrigin, GLuint aName) {
--- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -537,16 +537,17 @@ public: mIsOffscreen(aIsOffscreen), #ifdef USE_GLES2 mIsGLES2(true), #else mIsGLES2(false), #endif mIsGlobalSharedContext(false), mHasRobustness(false), + mContextLost(false), mVendor(-1), mDebugMode(0), mCreationFormat(aFormat), mSharedContext(aSharedContext), mOffscreenTexture(0), mFlipped(false), mBlitProgram(0), mBlitFramebuffer(0), @@ -590,16 +591,18 @@ public: bool MakeCurrent(bool aForce = false) { #ifdef DEBUG sCurrentGLContext = this; #endif return MakeCurrentImpl(aForce); } + bool IsContextLost() { return mContextLost; } + virtual bool SetupLookupFunction() = 0; virtual void WindowDestroyed() {} virtual void ReleaseSurface() {} void *GetUserData(void *aKey) { void *result = nsnull; @@ -1321,16 +1324,17 @@ public: } protected: bool mInitialized; bool mIsOffscreen; bool mIsGLES2; bool mIsGlobalSharedContext; bool mHasRobustness; + bool mContextLost; PRInt32 mVendor; enum { DebugEnabled = 1 << 0, DebugTrace = 1 << 1, DebugAbortOnError = 1 << 2 };
--- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -829,16 +829,20 @@ public: mSurface = CreateSurfaceForWindow(NULL, config); aForce = true; } #endif if (aForce || sEGLLibrary.fGetCurrentContext() != mContext) { succeeded = sEGLLibrary.fMakeCurrent(EGL_DISPLAY(), mSurface, mSurface, mContext); + if (!succeeded && sEGLLibrary.fGetError() == LOCAL_EGL_CONTEXT_LOST) { + mContextLost = true; + NS_WARNING("EGL context has been lost."); + } NS_ASSERTION(succeeded, "Failed to make GL context current!"); } return succeeded; } #ifdef MOZ_WIDGET_QT virtual bool
--- a/gfx/gl/GLContextProviderWGL.cpp +++ b/gfx/gl/GLContextProviderWGL.cpp @@ -117,16 +117,23 @@ CreateDummyWindow(HDC *aWindowDC = nsnul if (aWindowDC) { *aWindowDC = dc; } return win; } +static inline bool +HasExtension(const char* aExtensions, const char* aRequiredExtension) +{ + return GLContext::ListHasExtension( + reinterpret_cast<const GLubyte*>(aExtensions), aRequiredExtension); +} + bool WGLLibrary::EnsureInitialized() { if (mInitialized) return true; mozilla::ScopedGfxFeatureReporter reporter("WGL"); @@ -200,19 +207,57 @@ WGLLibrary::EnsureInitialized() if (!LibrarySymbolLoader::LoadSymbols(mOGLLibrary, &pixFmtSymbols[0], (LibrarySymbolLoader::PlatformLookupFunction)fGetProcAddress)) { // this isn't an error, just means that we don't have the pixel format extension fChoosePixelFormat = nsnull; } + LibrarySymbolLoader::SymLoadStruct extensionsSymbols[] = { + { (PRFuncPtr *) &fGetExtensionsString, { "wglGetExtensionsStringARB", NULL} }, + { NULL, { NULL } } + }; + + LibrarySymbolLoader::SymLoadStruct robustnessSymbols[] = { + { (PRFuncPtr *) &fCreateContextAttribs, { "wglCreateContextAttribsARB", NULL} }, + { NULL, { NULL } } + }; + + if (LibrarySymbolLoader::LoadSymbols(mOGLLibrary, &extensionsSymbols[0], + (LibrarySymbolLoader::PlatformLookupFunction)fGetProcAddress)) { + const char *wglExts = fGetExtensionsString(gSharedWindowDC); + if (wglExts && HasExtension(wglExts, "WGL_ARB_create_context")) { + LibrarySymbolLoader::LoadSymbols(mOGLLibrary, &robustnessSymbols[0], + (LibrarySymbolLoader::PlatformLookupFunction)fGetProcAddress); + if (HasExtension(wglExts, "WGL_ARB_create_context_robustness")) { + mHasRobustness = true; + } + } + } + // reset back to the previous context, just in case fMakeCurrent(curDC, curCtx); + if (mHasRobustness) { + fDeleteContext(gSharedWindowGLContext); + + int attribs[] = { + LOCAL_WGL_CONTEXT_FLAGS_ARB, LOCAL_WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB, + LOCAL_WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, LOCAL_WGL_LOSE_CONTEXT_ON_RESET_ARB, + NULL + }; + + gSharedWindowGLContext = fCreateContextAttribs(gSharedWindowDC, NULL, attribs); + if (!gSharedWindowGLContext) { + mHasRobustness = false; + gSharedWindowGLContext = fCreateContext(gSharedWindowDC); + } + } + mInitialized = true; // Call this to create the global GLContext instance, // and to check for errors. Note that this must happen /after/ // setting mInitialized to TRUE, or an infinite loop results. if (GLContextProviderWGL::GetGlobalContext() == nsnull) { mInitialized = false; return false; @@ -304,17 +349,17 @@ public: } virtual bool IsDoubleBuffered() { return mIsDoubleBuffered; } bool SupportsRobustness() { - return false; + return sWGLLibrary.HasRobustness(); } virtual bool SwapBuffers() { if (!mIsDoubleBuffered) return false; return ::SwapBuffers(mDC); } @@ -497,26 +542,45 @@ GLContextProviderWGL::CreateForWindow(ns * We need to make sure we call SetPixelFormat -after- calling * EnsureInitialized, otherwise it can load/unload the dll and * wglCreateContext will fail. */ HDC dc = (HDC)aWidget->GetNativeData(NS_NATIVE_GRAPHIC); SetPixelFormat(dc, gSharedWindowPixelFormat, NULL); - HGLRC context = sWGLLibrary.fCreateContext(dc); - if (!context) { - return nsnull; + HGLRC context; + + GLContextWGL *shareContext = GetGlobalContextWGL(); + + if (sWGLLibrary.HasRobustness()) { + int attribs[] = { + LOCAL_WGL_CONTEXT_FLAGS_ARB, LOCAL_WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB, + LOCAL_WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, LOCAL_WGL_LOSE_CONTEXT_ON_RESET_ARB, + NULL + }; + + context = sWGLLibrary.fCreateContextAttribs(dc, + shareContext ? shareContext->Context() : nsnull, + attribs); + if (!context && shareContext) { + context = sWGLLibrary.fCreateContextAttribs(dc, nsnull, attribs); + if (context) { + shareContext = nsnull; + } + } else { + context = sWGLLibrary.fCreateContext(dc); + if (context && shareContext && !sWGLLibrary.fShareLists(shareContext->Context(), context)) { + shareContext = nsnull; + } + } } - GLContextWGL *shareContext = GetGlobalContextWGL(); - if (shareContext && - !sWGLLibrary.fShareLists(shareContext->Context(), context)) - { - shareContext = nsnull; + if (!context) { + return nsnull; } nsRefPtr<GLContextWGL> glContext = new GLContextWGL(ContextFormat(ContextFormat::BasicRGB24), shareContext, dc, context); if (!glContext->Init()) { return nsnull; } @@ -592,17 +656,29 @@ CreatePBufferOffscreenContext(const gfxI pbattrs.Elements()); if (!pbuffer) { return nsnull; } HDC pbdc = sWGLLibrary.fGetPbufferDC(pbuffer); NS_ASSERTION(pbdc, "expected a dc"); - HGLRC context = sWGLLibrary.fCreateContext(pbdc); + HGLRC context; + if (sWGLLibrary.HasRobustness()) { + int attribs[] = { + LOCAL_WGL_CONTEXT_FLAGS_ARB, LOCAL_WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB, + LOCAL_WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, LOCAL_WGL_LOSE_CONTEXT_ON_RESET_ARB, + NULL + }; + + context = sWGLLibrary.fCreateContextAttribs(pbdc, nsnull, attribs); + } else { + context = sWGLLibrary.fCreateContext(pbdc); + } + if (!context) { sWGLLibrary.fDestroyPbuffer(pbuffer); return false; } nsRefPtr<GLContextWGL> glContext = new GLContextWGL(aFormat, nsnull, pbuffer, @@ -624,25 +700,38 @@ CreateWindowOffscreenContext(const Conte HDC dc; HWND win = CreateDummyWindow(&dc); if (!win) { return nsnull; } HGLRC context = sWGLLibrary.fCreateContext(dc); - if (!context) { - return nsnull; + if (sWGLLibrary.HasRobustness()) { + int attribs[] = { + LOCAL_WGL_CONTEXT_FLAGS_ARB, LOCAL_WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB, + LOCAL_WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, LOCAL_WGL_LOSE_CONTEXT_ON_RESET_ARB, + NULL + }; + + context = sWGLLibrary.fCreateContextAttribs(dc, shareContext->Context(), attribs); + } else { + context = sWGLLibrary.fCreateContext(dc); + if (context && shareContext && + !sWGLLibrary.fShareLists(shareContext->Context(), context)) + { + NS_WARNING("wglShareLists failed!"); + + sWGLLibrary.fDeleteContext(context); + DestroyWindow(win); + return nsnull; + } } - if (!sWGLLibrary.fShareLists(shareContext->Context(), context)) { - NS_WARNING("wglShareLists failed!"); - - sWGLLibrary.fDeleteContext(context); - DestroyWindow(win); + if (!context) { return nsnull; } nsRefPtr<GLContextWGL> glContext = new GLContextWGL(aFormat, shareContext, dc, context, win, true); return glContext.forget(); }
--- a/gfx/gl/GLDefs.h +++ b/gfx/gl/GLDefs.h @@ -3241,9 +3241,10 @@ typedef ptrdiff_t GLintptr; #define LOCAL_EGL_LOCK_USAGE_HINT_KHR 0x30C5 #define LOCAL_EGL_MAP_PRESERVE_PIXELS_KHR 0x30C4 #define LOCAL_EGL_READ_SURFACE_BIT_KHR 0x0001 #define LOCAL_EGL_WRITE_SURFACE_BIT_KHR 0x0002 #define LOCAL_EGL_LOCK_SURFACE_BIT_KHR 0x0080 #define LOCAL_EGL_CORE_NATIVE_ENGINE 0x305B #define LOCAL_EGL_READ 0x305A #define LOCAL_EGL_DRAW 0x3059 +#define LOCAL_EGL_CONTEXT_LOST 0x300E #endif
--- a/gfx/gl/WGLLibrary.h +++ b/gfx/gl/WGLLibrary.h @@ -37,17 +37,18 @@ #include "GLContext.h" namespace mozilla { namespace gl { class WGLLibrary { public: - WGLLibrary() : mInitialized(false), mOGLLibrary(nsnull) {} + WGLLibrary() : mInitialized(false), mOGLLibrary(nsnull), + mHasRobustness(false) {} typedef HGLRC (GLAPIENTRY * PFNWGLCREATECONTEXTPROC) (HDC); PFNWGLCREATECONTEXTPROC fCreateContext; typedef BOOL (GLAPIENTRY * PFNWGLDELETECONTEXTPROC) (HGLRC); PFNWGLDELETECONTEXTPROC fDeleteContext; typedef BOOL (GLAPIENTRY * PFNWGLMAKECURRENTPROC) (HDC, HGLRC); PFNWGLMAKECURRENTPROC fMakeCurrent; typedef PROC (GLAPIENTRY * PFNWGLGETPROCADDRESSPROC) (LPCSTR); @@ -71,21 +72,30 @@ public: typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEPROC) (HANDLE hPbuffer, int iBuffer); PFNWGLRELEASETEXIMAGEPROC fReleaseTexImage; typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATPROC) (HDC hdc, const int* piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); PFNWGLCHOOSEPIXELFORMATPROC fChoosePixelFormat; typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int* piAttributes, int *piValues); PFNWGLGETPIXELFORMATATTRIBIVPROC fGetPixelFormatAttribiv; + typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGPROC) (HDC hdc); + PFNWGLGETEXTENSIONSSTRINGPROC fGetExtensionsString; + + typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSPROC) (HDC hdc, HGLRC hShareContext, const int *attribList); + PFNWGLCREATECONTEXTATTRIBSPROC fCreateContextAttribs; + bool EnsureInitialized(); + bool HasRobustness() const { return mHasRobustness; } + private: bool mInitialized; PRLibrary *mOGLLibrary; + bool mHasRobustness; }; // a global WGLLibrary instance extern WGLLibrary sWGLLibrary; } /* namespace gl */ } /* namespace mozilla */
--- a/gfx/layers/opengl/LayerManagerOGL.cpp +++ b/gfx/layers/opengl/LayerManagerOGL.cpp @@ -995,19 +995,22 @@ LayerManagerOGL::SetupBackBuffer(int aWi mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mBackBufferFBO); mGLContext->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, mFBOTextureTarget, mBackBufferTexture, 0); - if (mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) != - LOCAL_GL_FRAMEBUFFER_COMPLETE) { - NS_RUNTIMEABORT("Error setting up framebuffer --- framebuffer not complete"); + GLenum result = mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + nsCAutoString msg; + msg.Append("Framebuffer not complete -- error 0x"); + msg.AppendInt(result, 16); + NS_RUNTIMEABORT(msg.get()); } mBackBufferSize.width = aWidth; mBackBufferSize.height = aHeight; } void LayerManagerOGL::CopyToTarget() @@ -1156,19 +1159,22 @@ LayerManagerOGL::CreateFBOWithTexture(co mGLContext->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, mFBOTextureTarget, tex, 0); // Making this call to fCheckFramebufferStatus prevents a crash on // PowerVR. See bug 695246. - if (mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) != - LOCAL_GL_FRAMEBUFFER_COMPLETE) { - NS_RUNTIMEABORT("Error setting up framebuffer --- framebuffer not complete"); + GLenum result = mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + nsCAutoString msg; + msg.Append("Framebuffer not complete -- error 0x"); + msg.AppendInt(result, 16); + NS_RUNTIMEABORT(msg.get()); } SetupPipeline(aRect.width, aRect.height, DontApplyWorldTransform); mGLContext->fScissor(0, 0, aRect.width, aRect.height); if (aInit == InitModeClear) { mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0); mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
--- a/image/test/browser/browser_image.js +++ b/image/test/browser/browser_image.js @@ -182,11 +182,12 @@ function nextTest() { if (tests.length == 0) { finish(); return; } tests.shift()(); } function test() { + ignoreAllUncaughtExceptions(); nextTest(); }
--- a/js/src/assembler/jit/ExecutableAllocator.cpp +++ b/js/src/assembler/jit/ExecutableAllocator.cpp @@ -33,25 +33,25 @@ size_t ExecutableAllocator::pageSize = 0 size_t ExecutableAllocator::largeAllocSize = 0; ExecutablePool::~ExecutablePool() { m_allocator->releasePoolPages(this); } void -ExecutableAllocator::getCodeStats(size_t& method, size_t& regexp, size_t& unused) const +ExecutableAllocator::sizeOfCode(size_t *method, size_t *regexp, size_t *unused) const { - method = 0; - regexp = 0; - unused = 0; + *method = 0; + *regexp = 0; + *unused = 0; for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront()) { ExecutablePool* pool = r.front(); - method += pool->m_mjitCodeMethod; - regexp += pool->m_mjitCodeRegexp; - unused += pool->m_allocation.size - pool->m_mjitCodeMethod - pool->m_mjitCodeRegexp; + *method += pool->m_mjitCodeMethod; + *regexp += pool->m_mjitCodeRegexp; + *unused += pool->m_allocation.size - pool->m_mjitCodeMethod - pool->m_mjitCodeRegexp; } } } #endif // HAVE(ASSEMBLER)
--- a/js/src/assembler/jit/ExecutableAllocator.h +++ b/js/src/assembler/jit/ExecutableAllocator.h @@ -225,17 +225,17 @@ public: void releasePoolPages(ExecutablePool *pool) { JS_ASSERT(pool->m_allocation.pages); if (destroyCallback) destroyCallback(pool->m_allocation.pages, pool->m_allocation.size); systemRelease(pool->m_allocation); m_pools.remove(m_pools.lookup(pool)); // this asserts if |pool| is not in m_pools } - void getCodeStats(size_t& method, size_t& regexp, size_t& unused) const; + void sizeOfCode(size_t *method, size_t *regexp, size_t *unused) const; void setDestroyCallback(DestroyCallback destroyCallback) { this->destroyCallback = destroyCallback; } private: static size_t pageSize; static size_t largeAllocSize;
--- a/js/src/configure.in +++ b/js/src/configure.in @@ -4559,16 +4559,28 @@ MOZ_ARG_ENABLE_BOOL(js-diagnostics, [ --enable-js-diagnostics Enable JS diagnostic assertions and breakpad data], JS_CRASH_DIAGNOSTICS=1, JS_CRASH_DIAGNOSTICS= ) if test -n "$JS_CRASH_DIAGNOSTICS"; then AC_DEFINE(JS_CRASH_DIAGNOSTICS) fi +dnl ======================================================== +dnl Enable changes that make the shell more deterministic +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(more-deterministic, +[ --enable-more-deterministic + Enable changes that make the shell more deterministic], + JS_MORE_DETERMINISTIC=1, + JS_MORE_DETERMINISTIC= ) +if test -n "$JS_MORE_DETERMINISTIC"; then + AC_DEFINE(JS_MORE_DETERMINISTIC) +fi + dnl ====================================================== dnl = Enable compiling with ccache dnl ====================================================== MOZ_ARG_WITH_STRING(ccache, [ --with-ccache[=path/to/ccache] Enable compiling with ccache], CCACHE=$withval, CCACHE="no")
--- a/js/src/ds/LifoAlloc.cpp +++ b/js/src/ds/LifoAlloc.cpp @@ -135,21 +135,20 @@ LifoAlloc::freeUnused() if (!lastUsed) { freeAll(); return; } latest = lastUsed; } /* Free all chunks after |latest|. */ - size_t freed = 0; - for (BumpChunk *victim = latest->next(); victim; victim = victim->next()) { + for (BumpChunk *victim = latest->next(); victim; victim = victim->next()) BumpChunk::delete_(victim); - freed++; - } + + latest->setNext(NULL); } LifoAlloc::BumpChunk * LifoAlloc::getOrCreateChunk(size_t n) { if (first) { /* Look for existing, unused BumpChunks to satisfy the request. */ while (latest->next()) {
--- a/js/src/ds/LifoAlloc.h +++ b/js/src/ds/LifoAlloc.h @@ -320,17 +320,18 @@ class LifoAlloc JS_DECLARE_NEW_METHODS(alloc, JS_ALWAYS_INLINE) /* Some legacy clients (ab)use LifoAlloc to act like a vector, see bug 688891. */ void *allocUnaligned(size_t n); void *reallocUnaligned(void *origPtr, size_t origSize, size_t incr); }; -class LifoAllocScope { +class LifoAllocScope +{ LifoAlloc *lifoAlloc; void *mark; bool shouldRelease; JS_DECL_USE_GUARD_OBJECT_NOTIFIER public: explicit LifoAllocScope(LifoAlloc *lifoAlloc JS_GUARD_OBJECT_NOTIFIER_PARAM)
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -121,17 +121,16 @@ BytecodeEmitter::BytecodeEmitter(Parser constList(parser->context), upvarIndices(parser->context), upvarMap(parser->context), globalScope(NULL), globalUses(parser->context), globalMap(parser->context), closedArgs(parser->context), closedVars(parser->context), - traceIndex(0), typesetCount(0) { flags = TCF_COMPILING; memset(&prolog, 0, sizeof prolog); memset(&main, 0, sizeof main); current = &main; firstLine = prolog.currentLine = main.currentLine = lineno; } @@ -1445,31 +1444,28 @@ UpdateLineNumberNotes(JSContext *cx, Byt return JS_TRUE; } static ptrdiff_t EmitTraceOp(JSContext *cx, BytecodeEmitter *bce, ParseNode *nextpn) { if (nextpn) { /* - * Try to give the JSOP_TRACE the same line number as the next + * Try to give the JSOP_LOOPHEAD the same line number as the next * instruction. nextpn is often a block, in which case the next * instruction typically comes from the first statement inside. */ JS_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) nextpn = nextpn->pn_head; if (!UpdateLineNumberNotes(cx, bce, nextpn->pn_pos.begin.lineno)) return -1; } - uint32 index = bce->traceIndex; - if (index < UINT16_MAX) - bce->traceIndex++; - return Emit3(cx, bce, JSOP_TRACE, UINT16_HI(index), UINT16_LO(index)); + return Emit1(cx, bce, JSOP_LOOPHEAD); } /* * If op is JOF_TYPESET (see the type barriers comment in jsinfer.h), reserve * a type set to store its result. */ static inline void CheckTypeSet(JSContext *cx, BytecodeEmitter *bce, JSOp op) @@ -5536,17 +5532,17 @@ EmitForIn(JSContext *cx, BytecodeEmitter /* * Jump down to the loop condition to minimize overhead assuming at * least one iteration, as the other loop forms do. */ ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0); if (jmp < 0) return false; - intN noteIndex2 = NewSrcNote(cx, bce, SRC_TRACE); + intN noteIndex2 = NewSrcNote(cx, bce, SRC_LOOPHEAD); if (noteIndex2 < 0) return false; top = bce->offset(); SET_STATEMENT_TOP(&stmtInfo, top); if (EmitTraceOp(cx, bce, NULL) < 0) return false; @@ -5677,17 +5673,17 @@ EmitNormalFor(JSContext *cx, BytecodeEmi jmp = EmitJump(cx, bce, JSOP_GOTO, 0); if (jmp < 0) return false; } top = bce->offset(); SET_STATEMENT_TOP(&stmtInfo, top); - intN noteIndex2 = NewSrcNote(cx, bce, SRC_TRACE); + intN noteIndex2 = NewSrcNote(cx, bce, SRC_LOOPHEAD); if (noteIndex2 < 0) return false; /* Emit code for the loop body. */ if (EmitTraceOp(cx, bce, forBody) < 0) return false; if (!EmitTree(cx, bce, forBody)) return false; @@ -6001,17 +5997,17 @@ frontend::EmitTree(JSContext *cx, Byteco */ PushStatement(bce, &stmtInfo, STMT_WHILE_LOOP, top); noteIndex = NewSrcNote(cx, bce, SRC_WHILE); if (noteIndex < 0) return JS_FALSE; jmp = EmitJump(cx, bce, JSOP_GOTO, 0); if (jmp < 0) return JS_FALSE; - noteIndex2 = NewSrcNote(cx, bce, SRC_TRACE); + noteIndex2 = NewSrcNote(cx, bce, SRC_LOOPHEAD); if (noteIndex2 < 0) return JS_FALSE; top = EmitTraceOp(cx, bce, pn->pn_right); if (top < 0) return JS_FALSE; if (!EmitTree(cx, bce, pn->pn_right)) return JS_FALSE; CHECK_AND_SET_JUMP_OFFSET_AT(cx, bce, jmp); @@ -6032,17 +6028,17 @@ frontend::EmitTree(JSContext *cx, Byteco break; case PNK_DOWHILE: /* Emit an annotated nop so we know to decompile a 'do' keyword. */ noteIndex = NewSrcNote(cx, bce, SRC_WHILE); if (noteIndex < 0 || Emit1(cx, bce, JSOP_NOP) < 0) return JS_FALSE; - noteIndex2 = NewSrcNote(cx, bce, SRC_TRACE); + noteIndex2 = NewSrcNote(cx, bce, SRC_LOOPHEAD); if (noteIndex2 < 0) return JS_FALSE; /* Compile the loop body. */ top = EmitTraceOp(cx, bce, pn->pn_left); if (top < 0) return JS_FALSE; PushStatement(bce, &stmtInfo, STMT_DO_LOOP, top);
--- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -660,17 +660,16 @@ struct BytecodeEmitter : public TreeCont GlobalUseVector globalUses; /* per-script global uses */ OwnedAtomIndexMapPtr globalMap; /* per-script map of global name to globalUses vector */ /* Vectors of pn_cookie slot values. */ typedef Vector<uint32, 8> SlotVector; SlotVector closedArgs; SlotVector closedVars; - uint16 traceIndex; /* index for the next JSOP_TRACE instruction */ uint16 typesetCount; /* Number of JOF_TYPESET opcodes generated */ BytecodeEmitter(Parser *parser, uintN lineno); bool init(JSContext *cx, TreeContext::InitBehavior ib = USED_AS_CODE_GENERATOR); JSContext *context() { return parser->context; } @@ -943,17 +942,17 @@ enum SrcNoteType { SRC_GENEXP = 1, /* JSOP_LAMBDA from generator expression */ SRC_IF_ELSE = 2, /* JSOP_IFEQ bytecode is from an if-then-else */ SRC_FOR_IN = 2, /* JSOP_GOTO to for-in loop condition from before loop (same arity as SRC_IF_ELSE) */ SRC_FOR = 3, /* JSOP_NOP or JSOP_POP in for(;;) loop head */ SRC_WHILE = 4, /* JSOP_GOTO to for or while loop condition from before loop, else JSOP_NOP at top of do-while loop */ - SRC_TRACE = 4, /* For JSOP_TRACE; includes distance to loop end */ + SRC_LOOPHEAD = 4, /* For JSOP_LOOPHEAD; includes distance to loop end */ SRC_CONTINUE = 5, /* JSOP_GOTO is a continue, not a break; also used on JSOP_ENDINIT if extra comma at end of array literal: [1,2,,]; JSOP_DUP continuing destructuring pattern */ SRC_DECL = 6, /* type of a declaration (var, const, let*) */ SRC_DESTRUCT = 6, /* JSOP_DUP starting a destructuring assignment operation, with SRC_DECL_* offset operand */ SRC_PCDELTA = 7, /* distance forward from comma-operator to
--- a/js/src/jit-test/tests/basic/bug657975.js +++ b/js/src/jit-test/tests/basic/bug657975.js @@ -15,24 +15,24 @@ x = 0; // bug 657984 #1 function f3(){ for(y in x); } trap(f3, 5, '') f3() // bug 657984 #2 function f4(){ for(y in x); } -trap(f4, 10, '') +trap(f4, 8, '') f4() // bug 658464 function f5() { for ([, x] in 0) {} } -trap(f5, 9, '') +trap(f5, 7, '') f5() // bug 658465 function f6() { "use strict"; print(Math.min(0, 1)); } trap(f6, 10, '') @@ -55,25 +55,25 @@ for (a in f8()) (function() {})() // bug 659043 f9 = (function() { for (let a = 0; a < 0; ++a) { for each(let w in []) {} } }) -trap(f9, 27, undefined); +trap(f9, 22, undefined); for (b in f9()) (function() {})() // bug 659233 f10 = (function() { while (h) { continue } }) trap(f10, 0, ''); try { f10() } catch (e) {} // bug 659337 f11 = Function("for (x = 0; x < 6; x++) { gc() }"); -trap(f11, 25, ''); +trap(f11, 23, ''); f11()
rename from js/src/jit-test/tests/basic/bug685321.js rename to js/src/jit-test/tests/basic/bug685321-1.js
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug685321-2.js @@ -0,0 +1,13 @@ +var o = {}; +function f() { + function g() { + x = 80; + return x; + }; + Object.defineProperty(o, "f", {get:g}); + var [x] = 0; + x = {}; + 2 + o.f; + print(x); +} +f();
--- a/js/src/jit-test/tests/basic/testBug683470.js +++ b/js/src/jit-test/tests/basic/testBug683470.js @@ -6,10 +6,10 @@ f = (function() { Object.defineProperty(this, "x", ({})); } for each(let d in [0, 0]) { try { b(d); } catch (e) {} } }) -trap(f, 54, undefined); +trap(f, 52, undefined); f()
--- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -88,19 +88,16 @@ ScriptAnalysis::addJump(JSContext *cx, u JS_ASSERT(code->stackDepth == stackDepth); code->jumpTarget = true; if (offset < *currentOffset) { jsbytecode *pc = script->code + offset; UntrapOpcode untrap(cx, script, pc); - if (JSOp(*pc) == JSOP_TRACE || JSOp(*pc) == JSOP_NOTRACE) - code->loopHead = true; - /* Scripts containing loops are never inlined. */ isInlineable = false; /* Don't follow back edges to bytecode which has already been analyzed. */ if (!code->analyzed) { if (*forwardJump == 0) *forwardJump = *currentOffset; *currentOffset = offset; @@ -665,17 +662,17 @@ ScriptAnalysis::analyzeLifetimes(JSConte if (loop && code->safePoint) loop->hasSafePoints = true; jsbytecode *pc = script->code + offset; UntrapOpcode untrap(cx, script, pc); JSOp op = (JSOp) *pc; - if ((op == JSOP_TRACE || op == JSOP_NOTRACE) && code->loop) { + if (op == JSOP_LOOPHEAD && code->loop) { /* * This is the head of a loop, we need to go and make sure that any * variables live at the head are live at the backedge and points prior. * For each such variable, look for the last lifetime segment in the body * and extend it to the end of the loop. */ JS_ASSERT(loop == code->loop); unsigned backedge = code->loop->backedge; @@ -812,17 +809,17 @@ ScriptAnalysis::analyzeLifetimes(JSConte if (loop && loop->entry == targetOffset && loop->entry > loop->lastBlock) loop->lastBlock = loop->entry; if (targetOffset < offset) { /* This is a loop back edge, no lifetime to pull in yet. */ #ifdef DEBUG JSOp nop = JSOp(script->code[targetOffset]); - JS_ASSERT(nop == JSOP_TRACE || nop == JSOP_NOTRACE || nop == JSOP_TRAP); + JS_ASSERT(nop == JSOP_LOOPHEAD || nop == JSOP_TRAP); #endif /* * If we already have a loop, it is an outer loop and we * need to prune the last block in the loop --- we do not * track 'continue' statements for outer loops. */ if (loop && loop->entry > loop->lastBlock) @@ -1169,17 +1166,17 @@ ScriptAnalysis::analyzeSSA(JSContext *cx offset = successorOffset; continue; } if (code->stackDepth > stackDepth) PodZero(stack + stackDepth, code->stackDepth - stackDepth); stackDepth = code->stackDepth; - if ((op == JSOP_TRACE || op == JSOP_NOTRACE) && code->loop) { + if (op == JSOP_LOOPHEAD && code->loop) { /* * Make sure there is a pending value array for phi nodes at the * loop head. We won't be able to clear these until we reach the * loop's back edge. * * We need phi nodes for all variables which might be modified * during the loop. This ensures that in the loop body we have * already updated state to reflect possible changes that happen
--- a/js/src/jsanalyze.h +++ b/js/src/jsanalyze.h @@ -93,19 +93,16 @@ class Bytecode public: Bytecode() { PodZero(this); } /* --------- Bytecode analysis --------- */ /* Whether there are any incoming jumps to this instruction. */ bool jumpTarget : 1; - /* There is a backwards jump to this instruction. */ - bool loopHead : 1; - /* Whether there is fallthrough to this instruction from a non-branching instruction. */ bool fallthrough : 1; /* Whether this instruction is the fall through point of a conditional jump. */ bool jumpFallthrough : 1; /* Whether this instruction can be branched to from a switch statement. Implies jumpTarget. */ bool switchTarget : 1;
--- a/js/src/jsapi-tests/testFuncCallback.cpp +++ b/js/src/jsapi-tests/testFuncCallback.cpp @@ -15,18 +15,17 @@ static void funcTransition(const JSFunction *, const JSScript *, const JSContext *cx, int entering) { if (entering > 0) { ++depth; ++enters; - if (! JS_ON_TRACE(cx)) - ++interpreted; + ++interpreted; } else { --depth; ++leaves; } } static JSBool called2 = false; @@ -88,22 +87,23 @@ BEGIN_TEST(testFuncCallback_bug507012) JS_SetFunctionCallback(cx, funcTransition); enters = leaves = depth = 0; EXEC("f(3)"); CHECK_EQUAL(enters, 1+3); CHECK_EQUAL(leaves, 1+3); CHECK_EQUAL(depth, 0); interpreted = enters = leaves = depth = 0; - // Check calls invoked while running on trace + // Check calls invoked while running on trace -- or now, perhaps on + // IonMonkey's equivalent, if it ever starts to exist? EXEC("function g () { ++x; }"); interpreted = enters = leaves = depth = 0; - EXEC("for (i = 0; i < 50; ++i) { g(); }"); - CHECK_EQUAL(enters, 1+50); - CHECK_EQUAL(leaves, 1+50); + EXEC("for (i = 0; i < 5000; ++i) { g(); }"); + CHECK_EQUAL(enters, 1+5000); + CHECK_EQUAL(leaves, 1+5000); CHECK_EQUAL(depth, 0); // Test nesting callbacks via JS_GetFunctionCallback() JS_SetFunctionCallback(cx, funcTransition); innerCallback = JS_GetFunctionCallback(cx); JS_SetFunctionCallback(cx, funcTransitionOverlay); EXEC("x = 0; function f (n) { if (n > 1) { f(n - 1); } }");
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1136,16 +1136,22 @@ JS_GetRuntime(JSContext *cx) } JS_PUBLIC_API(JSContext *) JS_ContextIterator(JSRuntime *rt, JSContext **iterp) { return js_ContextIterator(rt, JS_TRUE, iterp); } +JS_PUBLIC_API(JSContext *) +JS_ContextIteratorUnlocked(JSRuntime *rt, JSContext **iterp) +{ + return js_ContextIterator(rt, JS_FALSE, iterp); +} + JS_PUBLIC_API(JSVersion) JS_GetVersion(JSContext *cx) { return VersionNumber(cx->findVersion()); } JS_PUBLIC_API(JSVersion) JS_SetVersion(JSContext *cx, JSVersion newVersion) @@ -5292,16 +5298,30 @@ JS_SaveFrameChain(JSContext *cx) JS_PUBLIC_API(void) JS_RestoreFrameChain(JSContext *cx) { CHECK_REQUEST(cx); cx->stack.restoreFrameChain(); } +#ifdef MOZ_TRACE_JSCALLS +JS_PUBLIC_API(void) +JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb) +{ + cx->functionCallback = fcb; +} + +JS_PUBLIC_API(JSFunctionCallback) +JS_GetFunctionCallback(JSContext *cx) +{ + return cx->functionCallback; +} +#endif + /************************************************************************/ JS_PUBLIC_API(JSString *) JS_NewStringCopyN(JSContext *cx, const char *s, size_t n) { CHECK_REQUEST(cx); return js_NewStringCopyN(cx, s, n); }
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1113,16 +1113,24 @@ typedef void (* JSTraceDataOp)(JSTracer *trc, void *data); typedef JSBool (* JSOperationCallback)(JSContext *cx); typedef void (* JSErrorReporter)(JSContext *cx, const char *message, JSErrorReport *report); +#ifdef MOZ_TRACE_JSCALLS +typedef void +(* JSFunctionCallback)(const JSFunction *fun, + const JSScript *scr, + const JSContext *cx, + int entering); +#endif + /* * Possible exception types. These types are part of a JSErrorFormatString * structure. They define which error to throw in case of a runtime error. * JSEXN_NONE marks an unthrowable error. */ typedef enum JSExnType { JSEXN_NONE = -1, JSEXN_ERR, @@ -2096,16 +2104,19 @@ extern JS_PUBLIC_API(void) JS_SetContextPrivate(JSContext *cx, void *data); extern JS_PUBLIC_API(JSRuntime *) JS_GetRuntime(JSContext *cx); extern JS_PUBLIC_API(JSContext *) JS_ContextIterator(JSRuntime *rt, JSContext **iterp); +extern JS_PUBLIC_API(JSContext *) +JS_ContextIteratorUnlocked(JSRuntime *rt, JSContext **iterp); + extern JS_PUBLIC_API(JSVersion) JS_GetVersion(JSContext *cx); extern JS_PUBLIC_API(JSVersion) JS_SetVersion(JSContext *cx, JSVersion version); extern JS_PUBLIC_API(const char *) JS_VersionToString(JSVersion version); @@ -4172,16 +4183,33 @@ JS_IsRunning(JSContext *cx); * JS_SaveFrameChain deals with cx not having any code running on it. */ extern JS_PUBLIC_API(JSBool) JS_SaveFrameChain(JSContext *cx); extern JS_PUBLIC_API(void) JS_RestoreFrameChain(JSContext *cx); +#ifdef MOZ_TRACE_JSCALLS +/* + * The callback is expected to be quick and noninvasive. It should not + * trigger interrupts, turn on debugging, or produce uncaught JS + * exceptions. The state of the stack and registers in the context + * cannot be relied upon, since this callback may be invoked directly + * from either JIT. The 'entering' field means we are entering a + * function if it is positive, leaving a function if it is zero or + * negative. + */ +extern JS_PUBLIC_API(void) +JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb); + +extern JS_PUBLIC_API(JSFunctionCallback) +JS_GetFunctionCallback(JSContext *cx); +#endif /* MOZ_TRACE_JSCALLS */ + /************************************************************************/ /* * Strings. * * NB: JS_NewUCString takes ownership of bytes on success, avoiding a copy; * but on error (signified by null return), it leaves chars owned by the * caller. So the caller must free bytes in the error case, if it has no use
--- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -132,16 +132,45 @@ ThreadData::~ThreadData() bool ThreadData::init() { JS_ASSERT(!repCache); return stackSpace.init() && !!(dtoaState = js_NewDtoaState()); } +#ifdef JS_THREADSAFE +void +ThreadData::sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf, size_t *normal, size_t *temporary, + size_t *regexpCode, size_t *stackCommitted) +{ + /* + * There are other ThreadData members that could be measured; the ones + * below have been seen by DMD to be worth measuring. More stuff may be + * added later. + */ + + /* + * The computedSize is 0 because sizeof(DtoaState) isn't available here and + * it's not worth making it available. + */ + *normal = mallocSizeOf(dtoaState, /* sizeof(DtoaState) */0); + + *temporary = tempLifoAlloc.sizeOfExcludingThis(mallocSizeOf); + + size_t method = 0, regexp = 0, unused = 0; + if (execAlloc) + execAlloc->sizeOfCode(&method, ®exp, &unused); + JS_ASSERT(method == 0); /* this execAlloc is only used for regexp code */ + *regexpCode = regexp + unused; + + *stackCommitted = stackSpace.sizeOfCommitted(); +} +#endif + void ThreadData::triggerOperationCallback(JSRuntime *rt) { JS_ASSERT(rt == this->rt); /* * Use JS_ATOMIC_SET and JS_ATOMIC_INCREMENT in the hope that it ensures * the write will become immediately visible to other processors polling @@ -214,16 +243,24 @@ JSScript * js_GetCurrentScript(JSContext *cx) { return cx->hasfp() ? cx->fp()->maybeScript() : NULL; } #ifdef JS_THREADSAFE +void +JSThread::sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf, size_t *normal, size_t *temporary, + size_t *regexpCode, size_t *stackCommitted) +{ + data.sizeOfExcludingThis(mallocSizeOf, normal, temporary, regexpCode, stackCommitted); + *normal += mallocSizeOf(this, sizeof(JSThread)); +} + JSThread * js_CurrentThreadAndLockGC(JSRuntime *rt) { void *id = js_CurrentThreadId(); JS_LOCK_GC(rt); /* * We must not race with a GC that accesses cx->thread for JSContext @@ -770,16 +807,27 @@ js_ReportOutOfMemory(JSContext *cx) AutoAtomicIncrement incr(&cx->runtime->inOOMReport); onError(cx, msg, &report); } } JS_FRIEND_API(void) js_ReportOverRecursed(JSContext *maybecx) { +#ifdef JS_MORE_DETERMINISTIC + /* + * We cannot make stack depth deterministic across different + * implementations (e.g. JIT vs. interpreter will differ in + * their maximum stack depth). + * However, we can detect externally when we hit the maximum + * stack depth which is useful for external testing programs + * like fuzzers. + */ + fprintf(stderr, "js_ReportOverRecursed called\n"); +#endif if (maybecx) JS_ReportErrorNumber(maybecx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED); } void js_ReportAllocationOverflow(JSContext *maybecx) { if (maybecx) @@ -1650,16 +1698,28 @@ JSContext::updateJITEnabled() # if defined JS_CPU_X86 || defined JS_CPU_X64 && JSC::MacroAssemblerX86Common::getSSEState() >= JSC::MacroAssemblerX86Common::HasSSE2 # endif ; #endif } +size_t +JSContext::sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const +{ + /* + * There are other JSContext members that could be measured; the following + * ones have been found by DMD to be worth measuring. More stuff may be + * added later. + */ + return mallocSizeOf(this, sizeof(JSContext)) + + busyArrays.sizeOfExcludingThis(mallocSizeOf); +} + namespace js { AutoEnumStateRooter::~AutoEnumStateRooter() { if (!stateValue.isNull()) { DebugOnly<JSBool> ok = obj->enumerate(context, JSENUMERATE_DESTROY, &stateValue, 0); JS_ASSERT(ok);
--- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -222,16 +222,21 @@ struct ThreadData { void purge(JSContext *cx) { tempLifoAlloc.freeUnused(); gsnCache.purge(); /* FIXME: bug 506341. */ propertyCache.purge(cx); } +#ifdef JS_THREADSAFE + void sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf, size_t *normal, size_t *temporary, + size_t *regexpCode, size_t *stackCommitted); +#endif + /* This must be called with the GC lock held. */ void triggerOperationCallback(JSRuntime *rt); /* * Frames currently running in js::Interpret. See InterpreterFrames for * details. */ InterpreterFrames *interpreterFrames; @@ -252,17 +257,17 @@ struct JSThread { js::SystemAllocPolicy> Map; /* Linked list of all contexts in use on this thread. */ JSCList contextList; /* Opaque thread-id, from NSPR's PR_GetCurrentThread(). */ void *id; - /* Number of JS_SuspendRequest calls withot JS_ResumeRequest. */ + /* Number of JS_SuspendRequest calls without JS_ResumeRequest. */ unsigned suspendCount; # ifdef DEBUG unsigned checkRequestDepth; # endif /* Factored out of JSThread for !JS_THREADSAFE embedding in JSRuntime. */ js::ThreadData data; @@ -281,16 +286,20 @@ struct JSThread { ~JSThread() { /* The thread must have zero contexts. */ JS_ASSERT(JS_CLIST_IS_EMPTY(&contextList)); } bool init() { return data.init(); } + + JS_FRIEND_API(void) sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf, size_t *normal, + size_t *temporary, size_t *regexpCode, + size_t *stackCommitted); }; #define JS_THREAD_DATA(cx) (&(cx)->thread()->data) extern JSThread * js_CurrentThreadAndLockGC(JSRuntime *rt); /* @@ -1234,16 +1243,18 @@ struct JSContext #endif /* * See JS_SetTrustedPrincipals in jsapi.h. * Note: !cx->compartment is treated as trusted. */ bool runningWithTrustedPrincipals() const; + JS_FRIEND_API(size_t) sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const; + static inline JSContext *fromLinkField(JSCList *link) { JS_ASSERT(link); return reinterpret_cast<JSContext *>(uintptr_t(link) - offsetof(JSContext, link)); } #ifdef JS_THREADSAFE static inline JSContext *fromThreadLinks(JSCList *link) { JS_ASSERT(link);
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -44,17 +44,16 @@ #include "jsgcmark.h" #include "jsiter.h" #include "jsmath.h" #include "jsproxy.h" #include "jsscope.h" #include "jswatchpoint.h" #include "jswrapper.h" #include "assembler/wtf/Platform.h" -#include "assembler/jit/ExecutableAllocator.h" #include "yarr/BumpPointerAllocator.h" #include "methodjit/MethodJIT.h" #include "methodjit/PolyIC.h" #include "methodjit/MonoIC.h" #include "vm/Debugger.h" #include "jsgcinlines.h" #include "jsobjinlines.h" @@ -140,24 +139,24 @@ JSCompartment::ensureJaegerCompartmentEx cx->delete_(jc); return false; } jaegerCompartment_ = jc; return true; } void -JSCompartment::getMjitCodeStats(size_t& method, size_t& regexp, size_t& unused) const +JSCompartment::sizeOfCode(size_t *method, size_t *regexp, size_t *unused) const { if (jaegerCompartment_) { - jaegerCompartment_->execAlloc()->getCodeStats(method, regexp, unused); + jaegerCompartment_->execAlloc()->sizeOfCode(method, regexp, unused); } else { - method = 0; - regexp = 0; - unused = 0; + *method = 0; + *regexp = 0; + *unused = 0; } } #endif bool JSCompartment::wrap(JSContext *cx, Value *vp) { JS_ASSERT(cx->compartment == this);
--- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -228,17 +228,17 @@ struct JS_FRIEND_API(JSCompartment) { js::mjit::JaegerCompartment *jaegerCompartment() const { JS_ASSERT(jaegerCompartment_); return jaegerCompartment_; } bool ensureJaegerCompartmentExists(JSContext *cx); - void getMjitCodeStats(size_t& method, size_t& regexp, size_t& unused) const; + void sizeOfCode(size_t *method, size_t *regexp, size_t *unused) const; #endif /* * Shared scope property tree, and arena-pool for allocating its nodes. */ js::PropertyTree propertyTree; #ifdef DEBUG
--- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -1606,32 +1606,16 @@ bool js_ResumeVtune() { VTResume(); return true; } #endif /* MOZ_VTUNE */ -#ifdef MOZ_TRACE_JSCALLS - -JS_PUBLIC_API(void) -JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb) -{ - cx->functionCallback = fcb; -} - -JS_PUBLIC_API(JSFunctionCallback) -JS_GetFunctionCallback(JSContext *cx) -{ - return cx->functionCallback; -} - -#endif /* MOZ_TRACE_JSCALLS */ - JS_PUBLIC_API(void) JS_DumpBytecode(JSContext *cx, JSScript *script) { #if defined(DEBUG) LifoAlloc lifoAlloc(1024); Sprinter sprinter; INIT_SPRINTER(cx, &sprinter, &lifoAlloc, 0);
--- a/js/src/jsdbgapi.h +++ b/js/src/jsdbgapi.h @@ -573,38 +573,16 @@ js_StopVtune(); extern JS_FRIEND_API(bool) js_PauseVtune(); extern JS_FRIEND_API(bool) js_ResumeVtune(); #endif /* MOZ_VTUNE */ -#ifdef MOZ_TRACE_JSCALLS -typedef void (*JSFunctionCallback)(const JSFunction *fun, - const JSScript *scr, - const JSContext *cx, - int entering); - -/* - * The callback is expected to be quick and noninvasive. It should not - * trigger interrupts, turn on debugging, or produce uncaught JS - * exceptions. The state of the stack and registers in the context - * cannot be relied upon, since this callback may be invoked directly - * from either JIT. The 'entering' field means we are entering a - * function if it is positive, leaving a function if it is zero or - * negative. - */ -extern JS_PUBLIC_API(void) -JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb); - -extern JS_PUBLIC_API(JSFunctionCallback) -JS_GetFunctionCallback(JSContext *cx); -#endif /* MOZ_TRACE_JSCALLS */ - extern JS_PUBLIC_API(void) JS_DumpBytecode(JSContext *cx, JSScript *script); extern JS_PUBLIC_API(void) JS_DumpCompartmentBytecode(JSContext *cx); extern JS_PUBLIC_API(void) JS_DumpPCCounts(JSContext *cx, JSScript *script);
--- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -3378,18 +3378,17 @@ ScriptAnalysis::analyzeTypesBytecode(JSC } /* Add type constraints for the various opcodes. */ switch (op) { /* Nop bytecodes. */ case JSOP_POP: case JSOP_NOP: - case JSOP_TRACE: - case JSOP_NOTRACE: + case JSOP_LOOPHEAD: case JSOP_GOTO: case JSOP_GOTOX: case JSOP_IFEQ: case JSOP_IFEQX: case JSOP_IFNE: case JSOP_IFNEX: case JSOP_LINENO: case JSOP_DEFCONST:
--- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -1903,33 +1903,29 @@ ADD_EMPTY_CASE(JSOP_NOP) ADD_EMPTY_CASE(JSOP_UNUSED0) ADD_EMPTY_CASE(JSOP_CONDSWITCH) ADD_EMPTY_CASE(JSOP_TRY) #if JS_HAS_XML_SUPPORT ADD_EMPTY_CASE(JSOP_STARTXML) ADD_EMPTY_CASE(JSOP_STARTXMLEXPR) #endif ADD_EMPTY_CASE(JSOP_NULLBLOCKCHAIN) +ADD_EMPTY_CASE(JSOP_LOOPHEAD) END_EMPTY_CASES -BEGIN_CASE(JSOP_TRACE) -BEGIN_CASE(JSOP_NOTRACE) - /* No-op */ -END_CASE(JSOP_TRACE) - BEGIN_CASE(JSOP_LABEL) END_CASE(JSOP_LABEL) BEGIN_CASE(JSOP_LABELX) END_CASE(JSOP_LABELX) check_backedge: { CHECK_BRANCH(); - if (op != JSOP_NOTRACE && op != JSOP_TRACE) + if (op != JSOP_LOOPHEAD) DO_OP(); #ifdef JS_METHODJIT if (!useMethodJIT) DO_OP(); mjit::CompileStatus status = mjit::CanMethodJITAtBranch(cx, script, regs.fp(), regs.pc); if (status == mjit::Compile_Error)
--- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -4231,17 +4231,17 @@ Decompile(SprintStack *ss, jsbytecode *p * DecompileExpression or recursively from case * JSOP_{NOP,AND,OR}.) * * There's no special case for |if (genexp)| because the * compiler optimizes that to |if (true)|. */ pc2 = pc + len; op = JSOp(*pc2); - if (op == JSOP_TRACE || op == JSOP_NOP) + if (op == JSOP_LOOPHEAD || op == JSOP_NOP) pc2 += JSOP_NOP_LENGTH; LOCAL_ASSERT(pc2 < endpc || endpc < outer->code + outer->length); LOCAL_ASSERT(ss2.top == 1); ss2.opcodes[0] = JSOP_POP; if (pc2 == endpc) { op = JSOP_SETNAME; } else { @@ -5476,33 +5476,27 @@ ReconstructPCStack(JSContext *cx, JSScri namespace js { bool CallResultEscapes(jsbytecode *pc) { /* * If we see any of these sequences, the result is unused: * - call / pop - * - call / trace / pop * * If we see any of these sequences, the result is only tested for nullness: * - call / ifeq - * - call / trace / ifeq * - call / not / ifeq - * - call / trace / not / ifeq */ if (*pc != JSOP_CALL) return true; pc += JSOP_CALL_LENGTH; - if (*pc == JSOP_TRACE) - pc += JSOP_TRACE_LENGTH; - if (*pc == JSOP_POP) return false; if (*pc == JSOP_NOT) pc += JSOP_NOT_LENGTH; return (*pc != JSOP_IFEQ); }
--- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -276,18 +276,18 @@ OPDEF(JSOP_UNUSED0, 105,"unused0", /* The argument is the offset to the next statement and is used by IonMonkey. */ OPDEF(JSOP_LABEL, 106,"label", NULL, 3, 0, 0, 0, JOF_JUMP) OPDEF(JSOP_LABELX, 107,"labelx", NULL, 5, 0, 0, 0, JOF_JUMPX) /* Like JSOP_FUNAPPLY but for f.call instead of f.apply. */ OPDEF(JSOP_FUNCALL, 108,"funcall", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE|JOF_TYPESET) -/* This opcode stores an index that is unique to the given loop. */ -OPDEF(JSOP_TRACE, 109,"trace", NULL, 3, 0, 0, 0, JOF_UINT16) +/* This opcode is the target of the backwards jump for some loop. */ +OPDEF(JSOP_LOOPHEAD, 109,"loophead", NULL, 1, 0, 0, 0, JOF_BYTE) /* ECMA-compliant assignment ops. */ OPDEF(JSOP_BINDNAME, 110,"bindname", NULL, 3, 0, 1, 0, JOF_ATOM|JOF_NAME|JOF_SET) OPDEF(JSOP_SETNAME, 111,"setname", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_NAME|JOF_SET|JOF_DETECTING) /* Exception handling ops. */ OPDEF(JSOP_THROW, 112,js_throw_str, NULL, 1, 1, 0, 0, JOF_BYTE) @@ -429,158 +429,157 @@ OPDEF(JSOP_SETXMLNAME, 171,"setxmlnam OPDEF(JSOP_XMLNAME, 172,"xmlname", NULL, 1, 1, 1, 19, JOF_BYTE) OPDEF(JSOP_DESCENDANTS, 173,"descendants",NULL, 1, 2, 1, 18, JOF_BYTE) OPDEF(JSOP_FILTER, 174,"filter", NULL, 3, 1, 1, 0, JOF_JUMP) OPDEF(JSOP_ENDFILTER, 175,"endfilter", NULL, 3, 2, 1, 18, JOF_JUMP) OPDEF(JSOP_TOXML, 176,"toxml", NULL, 1, 1, 1, 19, JOF_BYTE) OPDEF(JSOP_TOXMLLIST, 177,"toxmllist", NULL, 1, 1, 1, 19, JOF_BYTE) OPDEF(JSOP_XMLTAGEXPR, 178,"xmltagexpr", NULL, 1, 1, 1, 0, JOF_BYTE) OPDEF(JSOP_XMLELTEXPR, 179,"xmleltexpr", NULL, 1, 1, 1, 0, JOF_BYTE) -OPDEF(JSOP_NOTRACE, 180,"notrace", NULL, 3, 0, 0, 0, JOF_UINT16) -OPDEF(JSOP_XMLCDATA, 181,"xmlcdata", NULL, 3, 0, 1, 19, JOF_ATOM) -OPDEF(JSOP_XMLCOMMENT, 182,"xmlcomment", NULL, 3, 0, 1, 19, JOF_ATOM) -OPDEF(JSOP_XMLPI, 183,"xmlpi", NULL, 3, 1, 1, 19, JOF_ATOM) -OPDEF(JSOP_DELDESC, 184,"deldesc", NULL, 1, 2, 1, 15, JOF_BYTE|JOF_ELEM|JOF_DEL) +OPDEF(JSOP_XMLCDATA, 180,"xmlcdata", NULL, 3, 0, 1, 19, JOF_ATOM) +OPDEF(JSOP_XMLCOMMENT, 181,"xmlcomment", NULL, 3, 0, 1, 19, JOF_ATOM) +OPDEF(JSOP_XMLPI, 182,"xmlpi", NULL, 3, 1, 1, 19, JOF_ATOM) +OPDEF(JSOP_DELDESC, 183,"deldesc", NULL, 1, 2, 1, 15, JOF_BYTE|JOF_ELEM|JOF_DEL) -OPDEF(JSOP_CALLPROP, 185,"callprop", NULL, 3, 1, 2, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_CALLOP|JOF_TMPSLOT3) +OPDEF(JSOP_CALLPROP, 184,"callprop", NULL, 3, 1, 2, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_CALLOP|JOF_TMPSLOT3) /* * These opcodes contain a reference to the current blockChain object. * They are emitted directly after instructions, such as DEFFUN, that need fast access to * the blockChain. The special NULLBLOCKCHAIN is needed because the JOF_OBJECT * does not permit NULL object references, since it stores an index into a table of * objects. */ -OPDEF(JSOP_BLOCKCHAIN, 186,"blockchain", NULL, 3, 0, 0, 0, JOF_OBJECT) -OPDEF(JSOP_NULLBLOCKCHAIN,187,"nullblockchain",NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_BLOCKCHAIN, 185,"blockchain", NULL, 3, 0, 0, 0, JOF_OBJECT) +OPDEF(JSOP_NULLBLOCKCHAIN,186,"nullblockchain",NULL, 1, 0, 0, 0, JOF_BYTE) /* * Opcode to hold 24-bit immediate integer operands. */ -OPDEF(JSOP_UINT24, 188,"uint24", NULL, 4, 0, 1, 16, JOF_UINT24) +OPDEF(JSOP_UINT24, 187,"uint24", NULL, 4, 0, 1, 16, JOF_UINT24) /* * Opcodes to allow 24-bit atom or object indexes. Whenever an index exceeds * the 16-bit limit, the index-accessing bytecode must be bracketed by * JSOP_INDEXBASE and JSOP_RESETBASE to provide the upper bits of the index. * See jsemit.c, EmitIndexOp. */ -OPDEF(JSOP_INDEXBASE, 189,"indexbase", NULL, 2, 0, 0, 0, JOF_UINT8|JOF_INDEXBASE) -OPDEF(JSOP_RESETBASE, 190,"resetbase", NULL, 1, 0, 0, 0, JOF_BYTE) -OPDEF(JSOP_RESETBASE0, 191,"resetbase0", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_INDEXBASE, 188,"indexbase", NULL, 2, 0, 0, 0, JOF_UINT8|JOF_INDEXBASE) +OPDEF(JSOP_RESETBASE, 189,"resetbase", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_RESETBASE0, 190,"resetbase0", NULL, 1, 0, 0, 0, JOF_BYTE) /* * Opcodes to help the decompiler deal with XML. */ -OPDEF(JSOP_STARTXML, 192,"startxml", NULL, 1, 0, 0, 0, JOF_BYTE) -OPDEF(JSOP_STARTXMLEXPR, 193,"startxmlexpr",NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_STARTXML, 191,"startxml", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_STARTXMLEXPR, 192,"startxmlexpr",NULL, 1, 0, 0, 0, JOF_BYTE) -OPDEF(JSOP_CALLELEM, 194, "callelem", NULL, 1, 2, 2, 18, JOF_BYTE |JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC|JOF_CALLOP) +OPDEF(JSOP_CALLELEM, 193, "callelem", NULL, 1, 2, 2, 18, JOF_BYTE |JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC|JOF_CALLOP) /* * Stop interpretation, emitted at end of script to save the threaded bytecode * interpreter an extra branch test on every DO_NEXT_OP (see jsinterp.c). */ -OPDEF(JSOP_STOP, 195,"stop", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_STOP, 194,"stop", NULL, 1, 0, 0, 0, JOF_BYTE) /* * Get an extant property value, throwing ReferenceError if the identified * property does not exist. */ -OPDEF(JSOP_GETXPROP, 196,"getxprop", NULL, 3, 1, 1, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET) +OPDEF(JSOP_GETXPROP, 195,"getxprop", NULL, 3, 1, 1, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET) -OPDEF(JSOP_CALLXMLNAME, 197, "callxmlname", NULL, 1, 1, 2, 19, JOF_BYTE|JOF_CALLOP) +OPDEF(JSOP_CALLXMLNAME, 196, "callxmlname", NULL, 1, 1, 2, 19, JOF_BYTE|JOF_CALLOP) /* * Specialized JSOP_TYPEOF to avoid reporting undefined for typeof(0, undef). */ -OPDEF(JSOP_TYPEOFEXPR, 198,"typeofexpr", NULL, 1, 1, 1, 15, JOF_BYTE|JOF_DETECTING) +OPDEF(JSOP_TYPEOFEXPR, 197,"typeofexpr", NULL, 1, 1, 1, 15, JOF_BYTE|JOF_DETECTING) /* * Block-local scope support. */ -OPDEF(JSOP_ENTERBLOCK, 199,"enterblock", NULL, 3, 0, -1, 0, JOF_OBJECT) -OPDEF(JSOP_LEAVEBLOCK, 200,"leaveblock", NULL, 5, -1, 0, 0, JOF_UINT16) +OPDEF(JSOP_ENTERBLOCK, 198,"enterblock", NULL, 3, 0, -1, 0, JOF_OBJECT) +OPDEF(JSOP_LEAVEBLOCK, 199,"leaveblock", NULL, 5, -1, 0, 0, JOF_UINT16) /* Jump to target if top of stack value isn't callable. */ -OPDEF(JSOP_IFCANTCALLTOP, 201,"ifcantcalltop",NULL, 3, 1, 1, 0, JOF_JUMP|JOF_DETECTING) +OPDEF(JSOP_IFCANTCALLTOP, 200,"ifcantcalltop",NULL, 3, 1, 1, 0, JOF_JUMP|JOF_DETECTING) /* Throws a TypeError if the value at the top of the stack is not primitive. */ -OPDEF(JSOP_PRIMTOP, 202,"primtop", NULL, 2, 1, 1, 0, JOF_INT8) +OPDEF(JSOP_PRIMTOP, 201,"primtop", NULL, 2, 1, 1, 0, JOF_INT8) /* * Generator and array comprehension support. */ -OPDEF(JSOP_GENERATOR, 203,"generator", NULL, 1, 0, 0, 0, JOF_BYTE) -OPDEF(JSOP_YIELD, 204,"yield", NULL, 1, 1, 1, 1, JOF_BYTE) -OPDEF(JSOP_ARRAYPUSH, 205,"arraypush", NULL, 3, 1, 0, 3, JOF_LOCAL) +OPDEF(JSOP_GENERATOR, 202,"generator", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_YIELD, 203,"yield", NULL, 1, 1, 1, 1, JOF_BYTE) +OPDEF(JSOP_ARRAYPUSH, 204,"arraypush", NULL, 3, 1, 0, 3, JOF_LOCAL) /* * Get the built-in function::foo namespace and push it. */ -OPDEF(JSOP_GETFUNNS, 206,"getfunns", NULL, 1, 0, 1, 19, JOF_BYTE) +OPDEF(JSOP_GETFUNNS, 205,"getfunns", NULL, 1, 0, 1, 19, JOF_BYTE) /* * Variant of JSOP_ENUMELEM for destructuring const (const [a, b] = ...). */ -OPDEF(JSOP_ENUMCONSTELEM, 207,"enumconstelem",NULL, 1, 3, 0, 3, JOF_BYTE|JOF_SET) +OPDEF(JSOP_ENUMCONSTELEM, 206,"enumconstelem",NULL, 1, 3, 0, 3, JOF_BYTE|JOF_SET) /* * Variant of JSOP_LEAVEBLOCK has a result on the stack above the locals, * which must be moved down when the block pops. */ -OPDEF(JSOP_LEAVEBLOCKEXPR,208,"leaveblockexpr",NULL, 5, -1, 1, 3, JOF_UINT16) +OPDEF(JSOP_LEAVEBLOCKEXPR,207,"leaveblockexpr",NULL, 5, -1, 1, 3, JOF_UINT16) \ /* * Optimize atom segments 1-3. These must be followed by JSOP_RESETBASE0 after * the opcode that they prefix. */ -OPDEF(JSOP_INDEXBASE1, 209,"indexbase1", NULL, 1, 0, 0, 0, JOF_BYTE |JOF_INDEXBASE) -OPDEF(JSOP_INDEXBASE2, 210,"indexbase2", NULL, 1, 0, 0, 0, JOF_BYTE |JOF_INDEXBASE) -OPDEF(JSOP_INDEXBASE3, 211,"indexbase3", NULL, 1, 0, 0, 0, JOF_BYTE |JOF_INDEXBASE) +OPDEF(JSOP_INDEXBASE1, 208,"indexbase1", NULL, 1, 0, 0, 0, JOF_BYTE |JOF_INDEXBASE) +OPDEF(JSOP_INDEXBASE2, 209,"indexbase2", NULL, 1, 0, 0, 0, JOF_BYTE |JOF_INDEXBASE) +OPDEF(JSOP_INDEXBASE3, 210,"indexbase3", NULL, 1, 0, 0, 0, JOF_BYTE |JOF_INDEXBASE) -OPDEF(JSOP_CALLGNAME, 212, "callgname", NULL, 3, 0, 2, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET|JOF_CALLOP|JOF_GNAME) -OPDEF(JSOP_CALLLOCAL, 213, "calllocal", NULL, 3, 0, 2, 19, JOF_LOCAL|JOF_NAME|JOF_CALLOP) -OPDEF(JSOP_CALLARG, 214, "callarg", NULL, 3, 0, 2, 19, JOF_QARG |JOF_NAME|JOF_CALLOP) -OPDEF(JSOP_BINDGNAME, 215, "bindgname", NULL, 3, 0, 1, 0, JOF_ATOM|JOF_NAME|JOF_SET|JOF_GNAME) +OPDEF(JSOP_CALLGNAME, 211, "callgname", NULL, 3, 0, 2, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET|JOF_CALLOP|JOF_GNAME) +OPDEF(JSOP_CALLLOCAL, 212, "calllocal", NULL, 3, 0, 2, 19, JOF_LOCAL|JOF_NAME|JOF_CALLOP) +OPDEF(JSOP_CALLARG, 213, "callarg", NULL, 3, 0, 2, 19, JOF_QARG |JOF_NAME|JOF_CALLOP) +OPDEF(JSOP_BINDGNAME, 214, "bindgname", NULL, 3, 0, 1, 0, JOF_ATOM|JOF_NAME|JOF_SET|JOF_GNAME) /* * Opcodes to hold 8-bit and 32-bit immediate integer operands. */ -OPDEF(JSOP_INT8, 216, "int8", NULL, 2, 0, 1, 16, JOF_INT8) -OPDEF(JSOP_INT32, 217, "int32", NULL, 5, 0, 1, 16, JOF_INT32) +OPDEF(JSOP_INT8, 215, "int8", NULL, 2, 0, 1, 16, JOF_INT8) +OPDEF(JSOP_INT32, 216, "int32", NULL, 5, 0, 1, 16, JOF_INT32) /* * Get the value of the 'length' property from a stacked object. */ -OPDEF(JSOP_LENGTH, 218, "length", NULL, 3, 1, 1, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET) +OPDEF(JSOP_LENGTH, 217, "length", NULL, 3, 1, 1, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET) /* * Push a JSVAL_HOLE value onto the stack, representing an omitted property in * an array literal (e.g. property 0 in the array [, 1]). This opcode is used * with the JSOP_NEWARRAY opcode. */ -OPDEF(JSOP_HOLE, 219, "hole", NULL, 1, 0, 1, 0, JOF_BYTE) +OPDEF(JSOP_HOLE, 218, "hole", NULL, 1, 0, 1, 0, JOF_BYTE) /* * Variants of JSOP_{DEF{,LOCAL}FUN,LAMBDA} optimized for the flat closure case. */ -OPDEF(JSOP_DEFFUN_FC, 220,"deffun_fc", NULL, 3, 0, 0, 0, JOF_OBJECT|JOF_DECLARING) -OPDEF(JSOP_DEFLOCALFUN_FC,221,"deflocalfun_fc",NULL, 5, 0, 0, 0, JOF_SLOTOBJECT|JOF_DECLARING|JOF_TMPSLOT) -OPDEF(JSOP_LAMBDA_FC, 222,"lambda_fc", NULL, 3, 0, 1, 19, JOF_OBJECT) +OPDEF(JSOP_DEFFUN_FC, 219,"deffun_fc", NULL, 3, 0, 0, 0, JOF_OBJECT|JOF_DECLARING) +OPDEF(JSOP_DEFLOCALFUN_FC,220,"deflocalfun_fc",NULL, 5, 0, 0, 0, JOF_SLOTOBJECT|JOF_DECLARING|JOF_TMPSLOT) +OPDEF(JSOP_LAMBDA_FC, 221,"lambda_fc", NULL, 3, 0, 1, 19, JOF_OBJECT) /* * Ensure that the value on the top of the stack is an object. The one * argument is an error message, defined in js.msg, that takes one parameter * (the decompilation of the primitive value). */ -OPDEF(JSOP_OBJTOP, 223,"objtop", NULL, 3, 0, 0, 0, JOF_UINT16) +OPDEF(JSOP_OBJTOP, 222,"objtop", NULL, 3, 0, 0, 0, JOF_UINT16) /* * Joined function object as method optimization support. */ -OPDEF(JSOP_SETMETHOD, 224,"setmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) -OPDEF(JSOP_INITMETHOD, 225,"initmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) +OPDEF(JSOP_SETMETHOD, 223,"setmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) +OPDEF(JSOP_INITMETHOD, 224,"initmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) -OPDEF(JSOP_SHARPINIT, 226,"sharpinit", NULL, 3, 0, 0, 0, JOF_UINT16|JOF_SHARPSLOT) +OPDEF(JSOP_SHARPINIT, 225,"sharpinit", NULL, 3, 0, 0, 0, JOF_UINT16|JOF_SHARPSLOT) /* Pop the stack, convert to a jsid (int or string), and push back. */ -OPDEF(JSOP_TOID, 227, "toid", NULL, 1, 1, 1, 0, JOF_BYTE) +OPDEF(JSOP_TOID, 226, "toid", NULL, 1, 1, 1, 0, JOF_BYTE)
--- a/js/src/jsxdrapi.h +++ b/js/src/jsxdrapi.h @@ -221,17 +221,17 @@ JS_XDRFindClassById(JSXDRState *xdr, uin * Bytecode version number. Increment the subtrahend whenever JS bytecode * changes incompatibly. * * This version number is XDR'd near the front of xdr bytecode and * aborts deserialization if there is a mismatch between the current * and saved versions. If deserialization fails, the data should be * invalidated if possible. */ -#define JSXDR_BYTECODE_VERSION (0xb973c0de - 98) +#define JSXDR_BYTECODE_VERSION (0xb973c0de - 99) /* * Library-private functions. */ extern JSBool js_XDRAtom(JSXDRState *xdr, JSAtom **atomp); JS_END_EXTERN_C
--- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -955,17 +955,18 @@ mjit::Compiler::finishThisUp(JITScript * JSC::LinkBuffer fullCode(result, codeSize, JSC::METHOD_CODE); JSC::LinkBuffer stubCode(result + masm.size(), stubcc.size(), JSC::METHOD_CODE); size_t nNmapLive = loopEntries.length(); for (size_t i = 0; i < script->length; i++) { Bytecode *opinfo = analysis->maybeCode(i); if (opinfo && opinfo->safePoint) { /* loopEntries cover any safe points which are at loop heads. */ - if (!cx->typeInferenceEnabled() || !opinfo->loopHead) + JSOp op = js_GetOpcode(cx, script, script->code + i); + if (!cx->typeInferenceEnabled() || op != JSOP_LOOPHEAD) nNmapLive++; } } /* Please keep in sync with JITScript::scriptDataSize! */ size_t dataSize = sizeof(JITScript) + sizeof(NativeMapEntry) * nNmapLive + sizeof(InlineFrame) * inlineFrames.length() + @@ -1018,17 +1019,18 @@ mjit::Compiler::finishThisUp(JITScript * NativeMapEntry *jitNmap = (NativeMapEntry *)cursor; jit->nNmapPairs = nNmapLive; cursor += sizeof(NativeMapEntry) * jit->nNmapPairs; size_t ix = 0; if (jit->nNmapPairs > 0) { for (size_t i = 0; i < script->length; i++) { Bytecode *opinfo = analysis->maybeCode(i); if (opinfo && opinfo->safePoint) { - if (cx->typeInferenceEnabled() && opinfo->loopHead) + JSOp op = js_GetOpcode(cx, script, script->code + i); + if (cx->typeInferenceEnabled() && op == JSOP_LOOPHEAD) continue; Label L = jumpMap[i]; JS_ASSERT(L.isSet()); jitNmap[ix].bcOff = i; jitNmap[ix].ncode = (uint8 *)(result + masm.distanceOf(L)); ix++; } } @@ -1577,17 +1579,17 @@ mjit::Compiler::generateMethod() fixedDoubleToAnyEntries.clear(); /* * Watch for fallthrough to the head of a 'do while' loop. * We don't know what register state we will be using at the head * of the loop so sync, branch, and fix it up after the loop * has been processed. */ - if (cx->typeInferenceEnabled() && analysis->getCode(PC).loopHead) { + if (cx->typeInferenceEnabled() && op == JSOP_LOOPHEAD) { frame.syncAndForgetEverything(); Jump j = masm.jump(); if (!startLoop(PC, j, PC)) return Compile_Error; } else { Label start = masm.label(); if (!frame.syncForBranch(PC, Uses(0))) return Compile_Error; @@ -1730,17 +1732,17 @@ mjit::Compiler::generateMethod() /* * Watch for gotos which are entering a 'for' or 'while' loop. * These jump to the loop condition test and are immediately * followed by the head of the loop. */ jsbytecode *next = PC + js_CodeSpec[op].length; if (cx->typeInferenceEnabled() && analysis->maybeCode(next) && - analysis->getCode(next).loopHead) { + js_GetOpcode(cx, script, next) == JSOP_LOOPHEAD) { frame.syncAndForgetEverything(); Jump j = masm.jump(); if (!startLoop(next, j, target)) return Compile_Error; } else { if (!frame.syncForBranch(target, Uses(0))) return Compile_Error; Jump j = masm.jump(); @@ -2807,25 +2809,24 @@ mjit::Compiler::generateMethod() prepareStubCall(Uses(frame.frameSlots())); masm.move(ImmPtr(fun), Registers::ArgReg1); INLINE_STUBCALL(stubs::FlatLambda, REJOIN_PUSH_OBJECT); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg); } END_CASE(JSOP_LAMBDA_FC) - BEGIN_CASE(JSOP_TRACE) - BEGIN_CASE(JSOP_NOTRACE) + BEGIN_CASE(JSOP_LOOPHEAD) { if (analysis->jumpTarget(PC)) { interruptCheckHelper(); recompileCheckHelper(); } } - END_CASE(JSOP_TRACE) + END_CASE(JSOP_LOOPHEAD) BEGIN_CASE(JSOP_DEBUGGER) { prepareStubCall(Uses(0)); masm.move(ImmPtr(PC), Registers::ArgReg1); INLINE_STUBCALL(stubs::DebuggerStatement, REJOIN_FALLTHROUGH); } END_CASE(JSOP_DEBUGGER)
--- a/js/src/methodjit/LoopState.cpp +++ b/js/src/methodjit/LoopState.cpp @@ -1777,35 +1777,36 @@ LoopState::analyzeLoopBody(unsigned fram temporariesStart = Max<uint32>(temporariesStart, ssa->getFrame(frame).depth + VALUES_PER_STACK_FRAME * 2 + script->nslots); if (script->failedBoundsCheck || analysis->localsAliasStack()) skipAnalysis = true; /* Analyze the entire script for frames inlined in the loop body. */ - unsigned start = (frame == CrossScriptSSA::OUTER_FRAME) ? lifetime->head + JSOP_TRACE_LENGTH : 0; + unsigned start = (frame == CrossScriptSSA::OUTER_FRAME) ? lifetime->head + JSOP_LOOPHEAD_LENGTH : 0; unsigned end = (frame == CrossScriptSSA::OUTER_FRAME) ? lifetime->backedge : script->length; unsigned offset = start; while (offset < end) { jsbytecode *pc = script->code + offset; uint32 successorOffset = offset + analyze::GetBytecodeLength(pc); analyze::Bytecode *opinfo = analysis->maybeCode(offset); if (!opinfo) { offset = successorOffset; continue; } + JSOp op = JSOp(*pc); + /* Don't do any hoisting for outer loops in case of nesting. */ - if (opinfo->loopHead) + if (op == JSOP_LOOPHEAD) skipAnalysis = true; - JSOp op = JSOp(*pc); switch (op) { case JSOP_CALL: { /* * Don't hoist within this loop unless calls at this site are inlined. * :XXX: also recognize native calls which will be inlined. */ bool foundInline = false; @@ -1890,18 +1891,17 @@ LoopState::analyzeLoopBody(unsigned fram break; } case JSOP_ENUMELEM: case JSOP_ENUMCONSTELEM: unknownModset = true; break; - case JSOP_TRACE: - case JSOP_NOTRACE: + case JSOP_LOOPHEAD: case JSOP_POP: case JSOP_ZERO: case JSOP_ONE: case JSOP_INT8: case JSOP_INT32: case JSOP_UINT16: case JSOP_UINT24: case JSOP_FALSE:
--- a/js/src/methodjit/TrampolineSparc.s +++ b/js/src/methodjit/TrampolineSparc.s @@ -107,24 +107,25 @@ throwpoline_exit: .size JaegerThrowpoline, . - JaegerThrowpoline ! void JaegerInterpolineScripted() .global JaegerInterpolineScripted .type JaegerInterpolineScripted, #function JaegerInterpolineScripted: ld [%l0 + 0x10], %l0 /* Load f->prev_ */ st %l0, [%fp - 36] /* Update f->regs->fp_ */ - ba JaegerInterpoline + ba interpoline_enter nop .size JaegerInterpolineScripted, . - JaegerInterpolineScripted ! void JaegerInterpoline() .global JaegerInterpoline .type JaegerInterpoline, #function JaegerInterpoline: +interpoline_enter: mov %o0,%o2 mov %l3,%o0 mov %l2,%o1 call js_InternalInterpret mov %sp,%o3 ld [%fp - 36], %l0 ld [%l0 + 0x18], %l2 /* fp->rval type */ ld [%l0 + 0x1c], %l3 /* fp->rval data */
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -1200,28 +1200,34 @@ GC(JSContext *cx, uintN argc, jsval *vp) { JSCompartment *comp = NULL; if (argc == 1) { Value arg = vp[2]; if (arg.isObject()) comp = UnwrapObject(&arg.toObject())->compartment(); } +#ifndef JS_MORE_DETERMINISTIC size_t preBytes = cx->runtime->gcBytes; +#endif JS_CompartmentGC(cx, comp); char buf[256]; +#ifdef JS_MORE_DETERMINISTIC + buf[0] = '\0'; +#else JS_snprintf(buf, sizeof(buf), "before %lu, after %lu, break %08lx\n", (unsigned long)preBytes, (unsigned long)cx->runtime->gcBytes, #ifdef HAVE_SBRK (unsigned long)sbrk(0) #else 0 #endif ); +#endif *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, buf)); return true; } static const struct ParamPair { const char *name; JSGCParamKey param; } paramMap[] = { @@ -3816,17 +3822,17 @@ JSBool MJitCodeStats(JSContext *cx, uintN argc, jsval *vp) { #ifdef JS_METHODJIT JSRuntime *rt = cx->runtime; AutoLockGC lock(rt); size_t n = 0, method, regexp, unused; for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) { - (*c)->getMjitCodeStats(method, regexp, unused); + (*c)->sizeOfCode(&method, ®exp, &unused); n += method + regexp + unused; } JS_SET_RVAL(cx, vp, INT_TO_JSVAL(n)); #else JS_SET_RVAL(cx, vp, JSVAL_VOID); #endif return true; }
--- a/js/src/tests/js1_8_5/regress/jstests.list +++ b/js/src/tests/js1_8_5/regress/jstests.list @@ -1,11 +1,13 @@ url-prefix ../../jsreftest.html?test=js1_8_5/regress/ script no-array-comprehension-length-limit.js +script regress-373843.js script regress-383902.js +require-or(debugMode,skip) script regress-476088.js script regress-500528.js script regress-533876.js script regress-541255-0.js script regress-541255-1.js script regress-541255-2.js script regress-541255-3.js script regress-541255-4.js script regress-541455.js
new file mode 100644 --- /dev/null +++ b/js/src/tests/js1_8_5/regress/regress-373843.js @@ -0,0 +1,13 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +if (typeof disassemble != 'undefined') +{ + var func = disassemble(function() { return "c\\d"; }) + + // The disassembled function will contain a bytecode "string" with the content of the string next to it. + // Check if that string isn't over-escaped i.e. \\ isn't escaped to \\\\ . + assertEq(func.indexOf("\\\\\\\\"), -1) +} + +reportCompare(0, 0, 'ok');
new file mode 100644 --- /dev/null +++ b/js/src/tests/js1_8_5/regress/regress-476088.js @@ -0,0 +1,17 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + + +var err; +try { + f = function() { var x; x.y; } + trap(f, 0, ""); + f(); +} catch (e) { + err = e; +} +assertEq(err instanceof TypeError, true); +assertEq(err.message, "x is undefined") + +reportCompare(0, 0, 'ok'); +
--- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -493,17 +493,17 @@ StackSpace::tryBumpLimit(JSContext *cx, { if (!ensureSpace(cx, REPORT_ERROR, from, nvals)) return false; *limit = conservativeEnd_; return true; } size_t -StackSpace::committedSize() +StackSpace::sizeOfCommitted() { #ifdef XP_WIN return (commitEnd_ - base_) * sizeof(Value); #else return (trustedEnd_ - base_) * sizeof(Value); #endif }
--- a/js/src/vm/StackSpace.h +++ b/js/src/vm/StackSpace.h @@ -176,17 +176,17 @@ class StackSpace */ inline Value *getStackLimit(JSContext *cx, MaybeReportError report); bool tryBumpLimit(JSContext *cx, Value *from, uintN nvals, Value **limit); /* Called during GC: mark segments, frames, and slots under firstUnused. */ void mark(JSTracer *trc); /* We only report the committed size; uncommitted size is uninteresting. */ - JS_FRIEND_API(size_t) committedSize(); + JS_FRIEND_API(size_t) sizeOfCommitted(); }; /*****************************************************************************/ class ContextStack { StackSegment *seg_; StackSpace *space_;
new file mode 100644 --- /dev/null +++ b/js/xpconnect/crashtests/705875.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<script> + +window.QueryInterface(Components.interfaces.nsIInterfaceRequestor); +window.getInterface(null); + +</script>
--- a/js/xpconnect/crashtests/crashtests.list +++ b/js/xpconnect/crashtests/crashtests.list @@ -29,8 +29,9 @@ load 558979.html load 582649.html load 601284-1.html load 603146-1.html load 603858-1.html load 608963.html load 616930-1.html load 639737-1.html load 648206-1.html +load 705875.html
--- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -3897,18 +3897,21 @@ nsXPCComponents_Utils::CreateObjectIn(co } JSBool FunctionWrapper(JSContext *cx, uintN argc, jsval *vp) { jsval v = js::GetFunctionNativeReserved(JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0); NS_ASSERTION(JSVAL_IS_OBJECT(v), "weird function"); - return JS_CallFunctionValue(cx, JS_THIS_OBJECT(cx, vp), v, - argc, JS_ARGV(cx, vp), vp); + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return JS_FALSE; + } + return JS_CallFunctionValue(cx, obj, v, argc, JS_ARGV(cx, vp), vp); } JSBool WrapCallable(JSContext *cx, JSObject *obj, jsid id, JSObject *propobj, jsval *vp) { JSFunction *fun = js::NewFunctionByIdWithReserved(cx, FunctionWrapper, 0, 0, JS_GetGlobalForObject(cx, obj), id); if (!fun)
--- a/js/xpconnect/src/XPCConvert.cpp +++ b/js/xpconnect/src/XPCConvert.cpp @@ -596,19 +596,21 @@ XPCConvert::JSData2Native(XPCCallContext XPC_LOG_ERROR(("XPCConvert::JSData2Native : void* params not supported")); NS_ERROR("void* params not supported"); return false; case nsXPTType::T_IID: { JSObject* obj; const nsID* pid=nsnull; + // There's no good reason to pass a null IID. if (JSVAL_IS_VOID(s) || JSVAL_IS_NULL(s)) { - *((const nsID**)d) = nsnull; - return true; + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + return false; } if (!JSVAL_IS_OBJECT(s) || (!(obj = JSVAL_TO_OBJECT(s))) || (!(pid = xpc_JSObjectToID(cx, obj))) || (!(pid = (const nsID*) nsMemory::Clone(pid, sizeof(nsID))))) { return false; } @@ -1029,16 +1031,19 @@ XPCConvert::NativeInterface2JSObject(XPC return false; jsval slim; if (ConstructSlimWrapper(ccx, aHelper, xpcscope, &slim)) { *d = slim; return true; } + if (JS_IsExceptionPending(cx)) + return false; + // Even if ConstructSlimWrapper returns false it might have created a // wrapper (while calling the PreCreate hook). In that case we need to // fall through because we either have a slim wrapper that needs to be // morphed or an XPCWrappedNative. flat = cache->GetWrapper(); } // We can't simply construct a slim wrapper. Go ahead and create an
--- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -1223,20 +1223,19 @@ CompartmentCallback(JSContext *cx, void CompartmentStats compartmentStats(cx, compartment); CompartmentStats *curr = data->compartmentStatsVector.AppendElement(compartmentStats); data->currCompartmentStats = curr; // Get the compartment-level numbers. #ifdef JS_METHODJIT size_t method, regexp, unused; - compartment->getMjitCodeStats(method, regexp, unused); - curr->mjitCodeMethod = method; - curr->mjitCodeRegexp = regexp; - curr->mjitCodeUnused = unused; + compartment->sizeOfCode(&method, ®exp, &unused); + JS_ASSERT(regexp == 0); /* this execAlloc is only used for method code */ + curr->mjitCode = method + unused; #endif JS_GetTypeInferenceMemoryStats(cx, compartment, &curr->typeInferenceMemory, MemoryReporterMallocSizeOf); curr->shapesCompartmentTables = js::GetCompartmentShapeTableSize(compartment, MemoryReporterMallocSizeOf); } void @@ -1534,25 +1533,53 @@ CollectCompartmentStatsForRuntime(JSRunt data->gcHeapChunkTotal = PRInt64(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) * js::gc::ChunkSize; js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback, ArenaCallback, CellCallback); js::IterateChunks(cx, data, ChunkCallback); - for (js::ThreadDataIter i(rt); !i.empty(); i.popFront()) - data->stackSize += i.threadData()->stackSpace.committedSize(); - - data->runtimeObjectSize = MemoryReporterMallocSizeOf(rt, sizeof(JSRuntime)); + data->runtimeObject = MemoryReporterMallocSizeOf(rt, sizeof(JSRuntime)); // Nb: we use sizeOfExcludingThis() because atomState.atoms is within // JSRuntime, and so counted when JSRuntime is counted. - data->atomsTableSize = + data->runtimeAtomsTable = rt->atomState.atoms.sizeOfExcludingThis(MemoryReporterMallocSizeOf); + + { + #ifndef JS_THREADSAFE + #error "This code assumes JS_THREADSAFE is defined" + #endif + + // Need the GC lock to call JS_ContextIteratorUnlocked() and to + // access rt->threads. + js::AutoLockGC lock(rt); + + JSContext *acx, *iter = NULL; + while ((acx = JS_ContextIteratorUnlocked(rt, &iter)) != NULL) { + data->runtimeContexts += + acx->sizeOfIncludingThis(MemoryReporterMallocSizeOf); + } + + for (JSThread::Map::Range r = rt->threads.all(); !r.empty(); r.popFront()) { + JSThread *thread = r.front().value; + size_t normal, temporary, regexpCode, stackCommitted; + thread->sizeOfIncludingThis(MemoryReporterMallocSizeOf, + &normal, + &temporary, + ®expCode, + &stackCommitted); + + data->runtimeThreadsNormal += normal; + data->runtimeThreadsTemporary += temporary; + data->runtimeThreadsRegexpCode += regexpCode; + data->runtimeThreadsStackCommitted += stackCommitted; + } + } } JS_DestroyContextNoGC(cx); // This is initialized to all bytes stored in used chunks, and then we // subtract used space from it each time around the loop. data->gcHeapChunkDirtyUnused = data->gcHeapChunkTotal - data->gcHeapChunkCleanUnused - @@ -1587,19 +1614,17 @@ CollectCompartmentStatsForRuntime(JSRunt stats.shapesExtraTreeTables + stats.shapesExtraDictTables + stats.shapesCompartmentTables; data->totalScripts += stats.gcHeapScripts + stats.scriptData; data->totalStrings += stats.gcHeapStrings + stats.stringChars; #ifdef JS_METHODJIT - data->totalMjit += stats.mjitCodeMethod + - stats.mjitCodeRegexp + - stats.mjitCodeUnused + + data->totalMjit += stats.mjitCode + stats.mjitData; #endif data->totalTypeInference += stats.gcHeapTypeObjects + stats.typeInferenceMemory.objects + stats.typeInferenceMemory.scripts + stats.typeInferenceMemory.tables; data->totalAnalysisTemp += stats.typeInferenceMemory.temporary; } @@ -1775,35 +1800,22 @@ ReportCompartmentStats(const Compartment "script-data"), nsIMemoryReporter::KIND_HEAP, stats.scriptData, "Memory allocated for JSScript bytecode and various variable-length " "tables." SLOP_BYTES_STRING, callback, closure); #ifdef JS_METHODJIT ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, - "mjit-code/method"), - nsIMemoryReporter::KIND_NONHEAP, stats.mjitCodeMethod, + "mjit-code"), + nsIMemoryReporter::KIND_NONHEAP, stats.mjitCode, "Memory used by the method JIT to hold the compartment's generated code.", callback, closure); ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, - "mjit-code/regexp"), - nsIMemoryReporter::KIND_NONHEAP, stats.mjitCodeRegexp, - "Memory used by the regexp JIT to hold the compartment's generated code.", - callback, closure); - - ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, - "mjit-code/unused"), - nsIMemoryReporter::KIND_NONHEAP, stats.mjitCodeUnused, - "Memory allocated by the method and/or regexp JIT to hold the " - "compartment's code, but which is currently unused.", - callback, closure); - - ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, "mjit-data"), nsIMemoryReporter::KIND_HEAP, stats.mjitData, "Memory used by the method JIT for the compartment's compilation data: " "JITScripts, native maps, and inline cache structs." SLOP_BYTES_STRING, callback, closure); #endif ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, @@ -1846,30 +1858,55 @@ ReportJSRuntimeStats(const IterateData & for (PRUint32 index = 0; index < data.compartmentStatsVector.Length(); index++) { ReportCompartmentStats(data.compartmentStatsVector[index], pathPrefix, callback, closure); } ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/runtime-object"), - nsIMemoryReporter::KIND_NONHEAP, data.runtimeObjectSize, + nsIMemoryReporter::KIND_HEAP, data.runtimeObject, "Memory used by the JSRuntime object." SLOP_BYTES_STRING, callback, closure); ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/atoms-table"), - nsIMemoryReporter::KIND_NONHEAP, data.atomsTableSize, - "Memory used by the atoms table.", + nsIMemoryReporter::KIND_HEAP, data.runtimeAtomsTable, + "Memory used by the atoms table." SLOP_BYTES_STRING, + callback, closure); + + ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/contexts"), + nsIMemoryReporter::KIND_HEAP, data.runtimeContexts, + "Memory used by JSContext objects and certain structures " + "hanging off them." SLOP_BYTES_STRING, + callback, closure); + + ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/threads/normal"), + nsIMemoryReporter::KIND_HEAP, data.runtimeThreadsNormal, + "Memory used by JSThread objects and their data, " + "excluding memory that is reported by " + "other reporters under 'explicit/js/runtime/'." SLOP_BYTES_STRING, callback, closure); - ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("stack"), - nsIMemoryReporter::KIND_NONHEAP, data.stackSize, - "Memory used for the JavaScript stack. This is the committed portion " - "of the stack; any uncommitted portion is not measured because it " - "hardly costs anything.", + ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/threads/temporary"), + nsIMemoryReporter::KIND_HEAP, data.runtimeThreadsTemporary, + "Memory held transiently in JSThreads and used during " + "compilation. It mostly holds parse nodes." + SLOP_BYTES_STRING, + callback, closure); + + ReportMemoryBytes0(pathPrefix + NS_LITERAL_CSTRING("runtime/threads/regexp-code"), + nsIMemoryReporter::KIND_NONHEAP, data.runtimeThreadsRegexpCode, + "Memory used by the regexp JIT to hold generated code.", + callback, closure); + + ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/threads/stack-committed"), + nsIMemoryReporter::KIND_NONHEAP, data.runtimeThreadsStackCommitted, + "Memory used for the thread stacks. This is the committed portions " + "of the stacks; any uncommitted portions are not measured because they " + "hardly cost anything.", callback, closure); ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("gc-heap-chunk-dirty-unused"), JS_GC_HEAP_KIND, data.gcHeapChunkDirtyUnused, "Memory on the garbage-collected JavaScript heap, within chunks with at " "least one allocated GC thing, that could be holding useful data but " "currently isn't. Memory here is mutually exclusive with memory reported" @@ -1994,30 +2031,29 @@ public: nsIMemoryReporter::KIND_OTHER, data.totalStrings, "Memory used for all string-related data. This is the sum of all " "compartments' 'gc-heap/strings' and 'string-chars' numbers.", callback, closure); #ifdef JS_METHODJIT ReportMemoryBytes(NS_LITERAL_CSTRING("js-total-mjit"), nsIMemoryReporter::KIND_OTHER, data.totalMjit, "Memory used by the method JIT. This is the sum of all compartments' " - "'mjit-code-method', 'mjit-code-regexp', 'mjit-code-unused' and '" - "'mjit-data' numbers.", + "'mjit-code', and 'mjit-data' numbers.", callback, closure); #endif ReportMemoryBytes(NS_LITERAL_CSTRING("js-total-type-inference"), nsIMemoryReporter::KIND_OTHER, data.totalTypeInference, "Non-transient memory used by type inference. This is the sum of all " "compartments' 'gc-heap/type-objects', 'type-inference/script-main', " "'type-inference/object-main' and 'type-inference/tables' numbers.", callback, closure); ReportMemoryBytes(NS_LITERAL_CSTRING("js-total-analysis-temporary"), nsIMemoryReporter::KIND_OTHER, data.totalAnalysisTemp, - "Transient memory used during type inference and compilation. " + "Memory used transiently during type inference and compilation. " "This is the sum of all compartments' 'analysis-temporary' numbers.", callback, closure); return NS_OK; } }; NS_IMPL_THREADSAFE_ISUPPORTS1(XPConnectJSCompartmentsMultiReporter
--- a/js/xpconnect/src/xpcpublic.h +++ b/js/xpconnect/src/xpcpublic.h @@ -216,30 +216,32 @@ struct CompartmentStats PRInt64 stringChars; PRInt64 shapesExtraTreeTables; PRInt64 shapesExtraDictTables; PRInt64 shapesExtraTreeShapeKids; PRInt64 shapesCompartmentTables; PRInt64 scriptData; #ifdef JS_METHODJIT - PRInt64 mjitCodeMethod; - PRInt64 mjitCodeRegexp; - PRInt64 mjitCodeUnused; + PRInt64 mjitCode; PRInt64 mjitData; #endif TypeInferenceMemoryStats typeInferenceMemory; }; struct IterateData { IterateData() - : runtimeObjectSize(0), - atomsTableSize(0), - stackSize(0), + : runtimeObject(0), + runtimeAtomsTable(0), + runtimeContexts(0), + runtimeThreadsNormal(0), + runtimeThreadsTemporary(0), + runtimeThreadsRegexpCode(0), + runtimeThreadsStackCommitted(0), gcHeapChunkTotal(0), gcHeapChunkCleanUnused(0), gcHeapChunkDirtyUnused(0), gcHeapChunkCleanDecommitted(0), gcHeapChunkDirtyDecommitted(0), gcHeapArenaUnused(0), gcHeapChunkAdmin(0), gcHeapUnusedPercentage(0), @@ -250,19 +252,23 @@ struct IterateData #ifdef JS_METHODJIT totalMjit(0), #endif totalTypeInference(0), totalAnalysisTemp(0), compartmentStatsVector(), currCompartmentStats(NULL) { } - PRInt64 runtimeObjectSize; - PRInt64 atomsTableSize; - PRInt64 stackSize; + PRInt64 runtimeObject; + PRInt64 runtimeAtomsTable; + PRInt64 runtimeContexts; + PRInt64 runtimeThreadsNormal; + PRInt64 runtimeThreadsTemporary; + PRInt64 runtimeThreadsRegexpCode; + PRInt64 runtimeThreadsStackCommitted; PRInt64 gcHeapChunkTotal; PRInt64 gcHeapChunkCleanUnused; PRInt64 gcHeapChunkDirtyUnused; PRInt64 gcHeapChunkCleanDecommitted; PRInt64 gcHeapChunkDirtyDecommitted; PRInt64 gcHeapArenaUnused; PRInt64 gcHeapChunkAdmin; PRInt64 gcHeapUnusedPercentage;
--- a/js/xpconnect/wrappers/XrayWrapper.cpp +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -445,17 +445,17 @@ nodePrincipal_getter(JSContext *cx, JSOb } return true; } static JSBool XrayToString(JSContext *cx, uintN argc, jsval *vp) { JSObject *wrapper = JS_THIS_OBJECT(cx, vp); - if (!IsWrapper(wrapper) || !WrapperFactory::IsXrayWrapper(wrapper)) { + if (!wrapper || !IsWrapper(wrapper) || !WrapperFactory::IsXrayWrapper(wrapper)) { JS_ReportError(cx, "XrayToString called on an incompatible object"); return false; } nsAutoString result(NS_LITERAL_STRING("[object XrayWrapper ")); if (mozilla::dom::binding::instanceIsProxy(&js::GetProxyPrivate(wrapper).toObject())) { JSString *wrapperStr = js::GetProxyHandler(wrapper)->obj_toString(cx, wrapper); size_t length;
--- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -1883,24 +1883,29 @@ DocumentViewerImpl::SetBounds(const nsIn NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); mBounds = aBounds; if (mWindow) { // When attached to a top level window, change the client area, not the // window frame. // Don't have the widget repaint. Layout will generate repaint requests // during reflow. - if (mAttachedToParent) - mWindow->ResizeClient(aBounds.x, aBounds.y, - aBounds.width, aBounds.height, - false); - else + if (mAttachedToParent) { + if (aBounds.x != 0 || aBounds.y != 0) { + mWindow->ResizeClient(aBounds.x, aBounds.y, + aBounds.width, aBounds.height, + false); + } else { + mWindow->ResizeClient(aBounds.width, aBounds.height, false); + } + } else { mWindow->Resize(aBounds.x, aBounds.y, aBounds.width, aBounds.height, false); + } } else if (mPresContext && mViewManager) { PRInt32 p2a = mPresContext->AppUnitsPerDevPixel(); mViewManager->SetWindowDimensions(NSIntPixelsToAppUnits(mBounds.width, p2a), NSIntPixelsToAppUnits(mBounds.height, p2a)); } // If there's a previous viewer, it's the one that's actually showing, // so be sure to resize it as well so it paints over the right area.
--- a/layout/base/tests/test_reftests_with_caret.html +++ b/layout/base/tests/test_reftests_with_caret.html @@ -96,17 +96,16 @@ function endTest() { var isWindows = /WINNT/.test(SpecialPowers.OS); var tests = [ [ 'bug389321-2.html' , 'bug389321-2-ref.html' ] , [ 'bug389321-3.html' , 'bug389321-3-ref.html' ] , [ 'bug482484.html' , 'bug482484-ref.html' ] , [ 'bug585922.html' , 'bug585922-ref.html' ] , - [ 'bug602141-3.html' , 'bug602141-3-ref.html' ] , [ 'bug632215-1.html' , 'bug632215-ref.html' ] , [ 'bug632215-2.html' , 'bug632215-ref.html' ] , [ 'bug633044-1.html' , 'bug633044-1-ref.html' ] , [ 'bug644428-1.html' , 'bug644428-1-ref.html' ] , ]; if (!isWindows) { tests.push([ 'bug106855-1.html' , 'bug106855-1-ref.html' ]); // bug 682837 @@ -114,16 +113,17 @@ if (!isWindows) { tests.push([ 'bug240933-1.html' , 'bug240933-1-ref.html' ]); // bug 681144 tests.push([ 'bug240933-2.html' , 'bug240933-1-ref.html' ]); // bug 681162 tests.push([ 'bug389321-1.html' , 'bug389321-1-ref.html' ]); // bug 683163 tests.push([ 'bug512295-1.html' , 'bug512295-1-ref.html' ]); // bug 681152 tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]); // bug 681331 tests.push([ 'bug597519-1.html' , 'bug597519-1-ref.html' ]); // bug 680579 tests.push([ 'bug602141-1.html' , 'bug602141-1-ref.html' ]); // bug 681334 tests.push([ 'bug602141-2.html' , 'bug602141-2-ref.html' ]); // bug 682836 + tests.push([ 'bug602141-3.html' , 'bug602141-3-ref.html' ]); // bug 683048 tests.push([ 'bug602141-4.html' , 'bug602141-4-ref.html' ]); // bug 681167 tests.push([ 'bug612271-1.html' , 'bug612271-ref.html' ]); // bug 681032 tests.push([ 'bug612271-2.html' , 'bug612271-ref.html' ]); // bug 680581 tests.push([ 'bug612271-3.html' , 'bug612271-ref.html' ]); // bug 681035 tests.push([ 'bug613433-1.html' , 'bug613433-ref.html' ]); // bug 681332 tests.push([ 'bug613433-2.html' , 'bug613433-ref.html' ]); // bug 681332 tests.push([ 'bug613433-3.html' , 'bug613433-ref.html' ]); // bug 681332 tests.push([ 'bug613807-1.html' , 'bug613807-1-ref.html' ]); // bug 680574
--- a/layout/build/nsLayoutCID.h +++ b/layout/build/nsLayoutCID.h @@ -86,23 +86,19 @@ // {3B581FD4-3497-426c-8F61-3658B971CB80} #define NS_TREEBOXOBJECT_CID \ { 0x3b581fd4, 0x3497, 0x426c, { 0x8f, 0x61, 0x36, 0x58, 0xb9, 0x71, 0xcb, 0x80 } } // {a35d1cd4-c505-4d2d-a0f9-aef00b7ce5a5} #define NS_CANVASRENDERINGCONTEXT2D_CID \ { 0xa35d1cd4, 0xc505, 0x4d2d, { 0xa0, 0xf9, 0xae, 0xf0, 0x0b, 0x7c, 0xe5, 0xa5 } } -// {BCD923C0-9788-4350-AC48-365F473161EB} -#define NS_CANVASRENDERINGCONTEXT2DTHEBES_CID \ -{ 0xbcd923c0, 0x9788, 0x4350, { 0xac, 0x48, 0x36, 0x5f, 0x47, 0x31, 0x61, 0xeb } } - -// {9052bb12-79b0-4bdd-8e60-7bf078026b6d} -#define NS_CANVASRENDERINGCONTEXT2DAZURE_CID \ -{0x9052bb12, 0x79b0, 0x4bdd, {0x8e, 0x60, 0x7b, 0xf0, 0x78, 0x02, 0x6b, 0x6d}} +// {BCD923C0-9788-4350-AC48-365F473161EB} +#define NS_CANVASRENDERINGCONTEXT2DTHEBES_CID \ +{ 0xbcd923c0, 0x9788, 0x4350, { 0xac, 0x48, 0x36, 0x5f, 0x47, 0x31, 0x61, 0xeb } } // {2fe88332-31c6-4829-b247-a07d8a73e80f} #define NS_CANVASRENDERINGCONTEXTWEBGL_CID \ { 0x2fe88332, 0x31c6, 0x4829, { 0xb2, 0x47, 0xa0, 0x7d, 0x8a, 0x7e, 0xe8, 0x0fe } } // {8b449142-1eab-4bfa-9830-fab6ebb09774} #define NS_DOMSTORAGE_CID \ { 0x8b449142, 0x1eab, 0x4bfa, { 0x98, 0x30, 0xfa, 0xb6, 0xeb, 0xb0, 0x97, 0x74 } }
--- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -760,17 +760,16 @@ NS_DEFINE_NAMED_CID(NS_PRECONTENTITERATO NS_DEFINE_NAMED_CID(NS_SUBTREEITERATOR_CID); NS_DEFINE_NAMED_CID(NS_HTMLIMAGEELEMENT_CID); NS_DEFINE_NAMED_CID(NS_HTMLOPTIONELEMENT_CID); #ifdef MOZ_MEDIA NS_DEFINE_NAMED_CID(NS_HTMLAUDIOELEMENT_CID); #endif NS_DEFINE_NAMED_CID(NS_CANVASRENDERINGCONTEXT2D_CID); NS_DEFINE_NAMED_CID(NS_CANVASRENDERINGCONTEXT2DTHEBES_CID); -NS_DEFINE_NAMED_CID(NS_CANVASRENDERINGCONTEXT2DAZURE_CID); NS_DEFINE_NAMED_CID(NS_CANVASRENDERINGCONTEXTWEBGL_CID); NS_DEFINE_NAMED_CID(NS_TEXT_ENCODER_CID); NS_DEFINE_NAMED_CID(NS_HTMLCOPY_TEXT_ENCODER_CID); NS_DEFINE_NAMED_CID(NS_XMLCONTENTSERIALIZER_CID); NS_DEFINE_NAMED_CID(NS_XHTMLCONTENTSERIALIZER_CID); NS_DEFINE_NAMED_CID(NS_HTMLCONTENTSERIALIZER_CID); NS_DEFINE_NAMED_CID(NS_PLAINTEXTSERIALIZER_CID); NS_DEFINE_NAMED_CID(MOZ_SANITIZINGHTMLSERIALIZER_CID);
--- a/layout/generic/nsTextFrameThebes.cpp +++ b/layout/generic/nsTextFrameThebes.cpp @@ -7079,30 +7079,63 @@ nsTextFrame::Reflow(nsPresContext* ReflowText(*aReflowState.mLineLayout, aReflowState.availableWidth, aReflowState.rendContext, aReflowState.mFlags.mBlinks, aMetrics, aStatus); NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); return NS_OK; } +#ifdef ACCESSIBILITY +/** + * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText. + */ +class NS_STACK_CLASS ReflowTextA11yNotifier +{ +public: + ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) : + mPresContext(aPresContext), mContent(aContent) + { + } + ~ReflowTextA11yNotifier() + { + nsAccessibilityService* accService = nsIPresShell::AccService(); + if (accService) { + accService->UpdateText(mPresContext->PresShell(), mContent); + } + } +private: + ReflowTextA11yNotifier(); + ReflowTextA11yNotifier(const ReflowTextA11yNotifier&); + ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&); + + nsIContent* mContent; + nsPresContext* mPresContext; +}; +#endif + void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, nsRenderingContext* aRenderingContext, bool aShouldBlink, nsHTMLReflowMetrics& aMetrics, nsReflowStatus& aStatus) { #ifdef NOISY_REFLOW ListTag(stdout); printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth); #endif nsPresContext* presContext = PresContext(); +#ifdef ACCESSIBILITY + // Schedule the update of accessible tree since rendered text might be changed. + ReflowTextA11yNotifier(presContext, mContent); +#endif + ///////////////////////////////////////////////////////////////////// // Set up flags and clear out state ///////////////////////////////////////////////////////////////////// // Clear out the reflow state flags in mState (without destroying // the TEXT_BLINK_ON bit). We also clear the whitespace flags because this // can change whether the frame maps whitespace-only text or not. RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); @@ -7603,24 +7636,16 @@ nsTextFrame::ReflowText(nsLineLayout& aL Invalidate(aMetrics.VisualOverflow()); #ifdef NOISY_REFLOW ListTag(stdout); printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.width, aMetrics.height, aMetrics.ascent, aStatus); #endif - -#ifdef ACCESSIBILITY - // Schedule the update of accessible tree when rendered text might be changed. - nsAccessibilityService* accService = nsIPresShell::AccService(); - if (accService) { - accService->UpdateText(presContext->PresShell(), mContent); - } -#endif } /* virtual */ bool nsTextFrame::CanContinueTextRun() const { // We can continue a text run through a text frame return true; }
--- a/layout/reftests/bugs/491323-1-ref.xul +++ b/layout/reftests/bugs/491323-1-ref.xul @@ -1,8 +1,15 @@ <?xml version="1.0"?> <window xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <hbox flex="1"> + <html:style> + /* Make sure our splitter's background doesn't depend on its hover styles */ + splitter { + background-color: yellow; + -moz-appearance: none; + } + </html:style> <splitter collapse="before" resizeafter="farthest" width="200" id="s"> </splitter> </hbox> </window>
--- a/layout/reftests/bugs/491323-1.xul +++ b/layout/reftests/bugs/491323-1.xul @@ -1,13 +1,20 @@ <?xml version="1.0"?> <window xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="loaded()"> <hbox flex="1"> + <html:style> + /* Make sure our splitter's background doesn't depend on its hover styles */ + splitter { + background-color: yellow; + -moz-appearance: none; + } + </html:style> <script> function loaded() { document.documentElement.getBoundingClientRect(); document.getElementById("s").setAttribute("state", "collapsed"); } </script> <iframe width="200" src="data:text/html,hello"/> <splitter collapse="before" resizeafter="farthest" width="200" id="s">
new file mode 100644 --- /dev/null +++ b/layout/tables/crashtests/451355-1.html @@ -0,0 +1,5 @@ +<style>table::after { content:"m"; }</style> +<table> +<select></select> +<th></th> +<colgroup>
new file mode 100644 --- /dev/null +++ b/layout/tables/crashtests/705996-1.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body onload="document.getElementById('x').setAttribute('rowspan', '3');"> +<table style="border-collapse: collapse;"><td colspan="3" id="x"></td></table> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/tables/crashtests/705996-2.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body onload="var a = document.getElementById('a'); a.parentNode.removeChild(a);"> +<table style="border-collapse: collapse;"><tr><td colspan="2" id="a"></td><td></td></tr></table> +</body> +</html>
--- a/layout/tables/crashtests/crashtests.list +++ b/layout/tables/crashtests/crashtests.list @@ -89,16 +89,17 @@ load 416845-1.xhtml load 416845-2.xhtml load 416845-3.html asserts(12) load 420654-1.xhtml # Bug 575011 load 423514-1.xhtml load 430374.html load 444431-1.html load 448988-1.xhtml load 450311-1.html +load 451355-1.html load 456041.html load 457115.html load 460637-1.xhtml load 460637-2.xhtml load 460637-3.xhtml load 467141-1.html load 488388-1.html load 512749-1.html @@ -109,8 +110,10 @@ load 573354-1.xhtml load 576890-1.html load 576890-2.html load 576890-3.html asserts(0-3) load 595758-1.xhtml # Bug 453871 load 595758-2.xhtml load 678447-1.html load 691824-1.xhtml load 695430-1.html +load 705996-1.html +load 705996-2.html
--- a/layout/tables/nsCellMap.cpp +++ b/layout/tables/nsCellMap.cpp @@ -2175,19 +2175,16 @@ void nsCellMap::ShrinkWithoutCell(nsTabl // get the rowspan and colspan from the cell map since the content may have changed bool zeroColSpan; PRUint32 numCols = aMap.GetColCount(); PRInt32 rowSpan = GetRowSpan(aRowIndex, aColIndex, false); PRUint32 colSpan = GetEffectiveColSpan(aMap, aRowIndex, aColIndex, zeroColSpan); PRUint32 endRowIndex = aRowIndex + rowSpan - 1; PRUint32 endColIndex = aColIndex + colSpan - 1; - SetDamageArea(aColIndex, aRgFirstRowIndex + aRowIndex, - NS_MAX(0, aMap.GetColCount() - aColIndex - 1), - 1 + endRowIndex - aRowIndex, aDamageArea); if (aMap.mTableFrame.HasZeroColSpans()) { aMap.mTableFrame.SetNeedColSpanExpansion(true); } // adjust the col counts due to the deleted cell before removing it for (colX = aColIndex; colX <= endColIndex; colX++) { nsColInfo* colInfo = aMap.GetColInfoAt(colX); @@ -2248,16 +2245,19 @@ void nsCellMap::ShrinkWithoutCell(nsTabl if (colInfo) { colInfo->mNumCellsSpan--; } } } } } aMap.RemoveColsAtEnd(); + SetDamageArea(aColIndex, aRgFirstRowIndex + aRowIndex, + NS_MAX(0, aMap.GetColCount() - aColIndex - 1), + 1 + endRowIndex - aRowIndex, aDamageArea); } void nsCellMap::RebuildConsideringRows(nsTableCellMap& aMap, PRInt32 aStartRowIndex, nsTArray<nsTableRowFrame*>* aRowsToInsert, PRInt32 aNumRowsToRemove) {
--- a/layout/xul/base/src/nsMenuPopupFrame.cpp +++ b/layout/xul/base/src/nsMenuPopupFrame.cpp @@ -110,16 +110,17 @@ NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame // // nsMenuPopupFrame ctor // nsMenuPopupFrame::nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext) :nsBoxFrame(aShell, aContext), mCurrentMenu(nsnull), mPrefSize(-1, -1), + mLastClientOffset(0, 0), mPopupType(ePopupTypePanel), mPopupState(ePopupClosed), mPopupAlignment(POPUPALIGNMENT_NONE), mPopupAnchor(POPUPALIGNMENT_NONE), mConsumeRollupEvent(nsIPopupBoxObject::ROLLUP_DEFAULT), mFlipBoth(false), mIsOpenChanged(false), mIsContextMenu(false), @@ -481,31 +482,16 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayou nsPresContext* pc = PresContext(); if (isOpen) { nsIView* view = GetView(); nsIViewManager* viewManager = view->GetViewManager(); nsRect rect = GetRect(); rect.x = rect.y = 0; - // Increase the popup's view size to account for any titlebar or borders. - // XXXndeakin this should really be accounted for earlier in - // SetPopupPosition so that this extra size is accounted for when flipping - // or resizing the popup due to it being too large, but that can be a - // followup bug. - if (mPopupType == ePopupTypePanel && view) { - nsIWidget* widget = view->GetWidget(); - if (widget) { - nsIntSize popupSize = nsIntSize(pc->AppUnitsToDevPixels(rect.width), - pc->AppUnitsToDevPixels(rect.height)); - popupSize = widget->ClientToWindowSize(popupSize); - rect.width = pc->DevPixelsToAppUnits(popupSize.width); - rect.height = pc->DevPixelsToAppUnits(popupSize.height); - } - } viewManager->ResizeView(view, rect); viewManager->SetViewVisibility(view, nsViewVisibility_kShow); mPopupState = ePopupOpenAndVisible; nsContainerFrame::SyncFrameViewProperties(pc, this, nsnull, view, 0); } // finally, if the popup just opened, send a popupshown event @@ -1313,30 +1299,31 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr nsPoint viewPoint = screenPoint - rootScreenRect.TopLeft(); // snap the view's position to device pixels, see bug 622507 viewPoint.x = presContext->RoundAppUnitsToNearestDevPixels(viewPoint.x); viewPoint.y = presContext->RoundAppUnitsToNearestDevPixels(viewPoint.y); nsIView* view = GetView(); NS_ASSERTION(view, "popup with no view"); - presContext->GetPresShell()->GetViewManager()-> - MoveViewTo(view, viewPoint.x, viewPoint.y); // Offset the position by the width and height of the borders and titlebar. // Even though GetClientOffset should return (0, 0) when there is no // titlebar or borders, we skip these calculations anyway for non-panels // to save time since they will never have a titlebar. nsIWidget* widget = view->GetWidget(); if (mPopupType == ePopupTypePanel && widget) { - nsIntPoint offset = widget->GetClientOffset(); - viewPoint.x += presContext->DevPixelsToAppUnits(offset.x); - viewPoint.y += presContext->DevPixelsToAppUnits(offset.y); + mLastClientOffset = widget->GetClientOffset(); + viewPoint.x += presContext->DevPixelsToAppUnits(mLastClientOffset.x); + viewPoint.y += presContext->DevPixelsToAppUnits(mLastClientOffset.y); } + presContext->GetPresShell()->GetViewManager()-> + MoveViewTo(view, viewPoint.x, viewPoint.y); + // Now that we've positioned the view, sync up the frame's origin. nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame)); if (sizedToPopup) { nsBoxLayoutState state(PresContext()); // XXXndeakin can parentSize.width still extend outside? SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height)); } @@ -1758,26 +1745,24 @@ nsMenuPopupFrame::LockMenuUntilClosed(bo if (parent && parent->GetType() == nsGkAtoms::menuFrame) { nsMenuParent* parentParent = static_cast<nsMenuFrame*>(parent)->GetMenuParent(); if (parentParent) { parentParent->LockMenuUntilClosed(aLock); } } } -NS_IMETHODIMP -nsMenuPopupFrame::GetWidget(nsIWidget **aWidget) +nsIWidget* +nsMenuPopupFrame::GetWidget() { nsIView * view = GetRootViewForPopup(this); if (!view) - return NS_OK; + return nsnull; - *aWidget = view->GetWidget(); - NS_IF_ADDREF(*aWidget); - return NS_OK; + return view->GetWidget(); } void nsMenuPopupFrame::AttachedDismissalListener() { mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT; } @@ -1866,18 +1851,21 @@ nsMenuPopupFrame::DestroyFrom(nsIFrame* nsBoxFrame::DestroyFrom(aDestructRoot); } void nsMenuPopupFrame::MoveTo(PRInt32 aLeft, PRInt32 aTop, bool aUpdateAttrs) { - if (mScreenXPos == aLeft && mScreenYPos == aTop) + nsIWidget* widget = GetWidget(); + if ((mScreenXPos == aLeft && mScreenYPos == aTop) && + (!widget || widget->GetClientOffset() == mLastClientOffset)) { return; + } // reposition the popup at the specified coordinates. Don't clear the anchor // and position, because the popup can be reset to its anchor position by // using (-1, -1) as coordinates. Subtract off the margin as it will be // added to the position when SetPopupPosition is called. nsMargin margin(0, 0, 0, 0); GetStyleMargin()->GetMargin(margin); nsPresContext* presContext = PresContext();
--- a/layout/xul/base/src/nsMenuPopupFrame.h +++ b/layout/xul/base/src/nsMenuPopupFrame.h @@ -171,17 +171,17 @@ public: virtual bool IsContextMenu() { return mIsContextMenu; } virtual bool MenuClosed() { return true; } virtual void LockMenuUntilClosed(bool aLock); virtual bool IsMenuLocked() { return mIsMenuLocked; } - NS_IMETHOD GetWidget(nsIWidget **aWidget); + nsIWidget* GetWidget(); // The dismissal listener gets created and attached to the window. void AttachedDismissalListener(); // Overridden methods NS_IMETHOD Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow); @@ -347,16 +347,19 @@ public: nsIContent* GetAnchor() const { return mAnchorContent; } // Return the screen coordinates of the popup, or (-1, -1) if anchored. nsIntPoint ScreenPosition() const { return nsIntPoint(mScreenXPos, mScreenYPos); } NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists); + + nsIntPoint GetLastClientOffset() const { return mLastClientOffset; } + protected: // returns the popup's level. nsPopupLevel PopupLevel(bool aIsNoAutoHide) const; // redefine to tell the box system not to move the views. virtual void GetLayoutFlags(PRUint32& aFlags); @@ -430,16 +433,20 @@ protected: nsSize mPrefSize; // the position of the popup. The screen coordinates, if set to values other // than -1, override mXPos and mYPos. PRInt32 mXPos; PRInt32 mYPos; PRInt32 mScreenXPos; PRInt32 mScreenYPos; + // The value of the client offset of our widget the last time we positioned + // ourselves. We store this so that we can detect when it changes but the + // position of our widget didn't change. + nsIntPoint mLastClientOffset; nsPopupType mPopupType; // type of popup nsPopupState mPopupState; // open state of the popup // popup alignment relative to the anchor node PRInt8 mPopupAlignment; PRInt8 mPopupAnchor; // One of nsIPopupBoxObject::ROLLUP_DEFAULT/ROLLUP_CONSUME/ROLLUP_NO_CONSUME
--- a/layout/xul/base/src/nsResizerFrame.cpp +++ b/layout/xul/base/src/nsResizerFrame.cpp @@ -254,18 +254,17 @@ nsResizerFrame::HandleEvent(nsPresContex appUnitsRect.width = mRect.width; if (appUnitsRect.height < mRect.height && mouseMove.y) appUnitsRect.height = mRect.height; nsIntRect cssRect = appUnitsRect.ToInsidePixels(nsPresContext::AppUnitsPerCSSPixel()); nsIntRect oldRect; nsWeakFrame weakFrame(menuPopupFrame); if (menuPopupFrame) { - nsCOMPtr<nsIWidget> widget; - menuPopupFrame->GetWidget(getter_AddRefs(widget)); + nsCOMPtr<nsIWidget> widget = menuPopupFrame->GetWidget(); if (widget) widget->GetScreenBounds(oldRect); // convert the new rectangle into outer window coordinates nsIntPoint clientOffset = widget->GetClientOffset(); rect.x -= clientOffset.x; rect.y -= clientOffset.y; }
--- a/layout/xul/base/src/nsTitleBarFrame.cpp +++ b/layout/xul/base/src/nsTitleBarFrame.cpp @@ -156,18 +156,17 @@ nsTitleBarFrame::HandleEvent(nsPresConte nsIFrame* parent = GetParent(); while (parent && parent->GetType() != nsGkAtoms::menuPopupFrame) parent = parent->GetParent(); // if the titlebar is in a popup, move the popup frame, otherwise // move the widget associated with the window if (parent) { nsMenuPopupFrame* menuPopupFrame = static_cast<nsMenuPopupFrame*>(parent); - nsCOMPtr<nsIWidget> widget; - menuPopupFrame->GetWidget(getter_AddRefs(widget)); + nsCOMPtr<nsIWidget> widget = menuPopupFrame->GetWidget(); nsIntRect bounds; widget->GetScreenBounds(bounds); menuPopupFrame->MoveTo(bounds.x + nsMoveBy.x, bounds.y + nsMoveBy.y, false); } else { nsIPresShell* presShell = aPresContext->PresShell(); nsPIDOMWindow *window = presShell->GetDocument()->GetWindow(); if (window) {
--- a/layout/xul/base/src/nsXULPopupManager.cpp +++ b/layout/xul/base/src/nsXULPopupManager.cpp @@ -278,18 +278,17 @@ nsXULPopupManager::GetSubmenuWidgetChain // this method is used by the widget code to determine the list of popups // that are open. If a mouse click occurs outside one of these popups, the // panels will roll up. If the click is inside a popup, they will not roll up PRUint32 count = 0, sameTypeCount = 0; NS_ASSERTION(aWidgetChain, "null parameter"); nsMenuChainItem* item = GetTopVisibleMenu(); while (item) { - nsCOMPtr<nsIWidget> widget; - item->Frame()->GetWidget(getter_AddRefs(widget)); + nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget(); NS_ASSERTION(widget, "open popup has no widget"); aWidgetChain->AppendElement(widget.get()); // In the case when a menulist inside a panel is open, clicking in the // panel should still roll up the menu, so if a different type is found, // stop scanning. nsMenuChainItem* parent = item->GetParent(); if (!sameTypeCount) { count++; @@ -354,17 +353,19 @@ nsXULPopupManager::PopupMoved(nsIFrame* { nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame); if (!menuPopupFrame) return; // Don't do anything if the popup is already at the specified location. This // prevents recursive calls when a popup is positioned. nsIntPoint currentPnt = menuPopupFrame->ScreenPosition(); - if (aPnt.x != currentPnt.x || aPnt.y != currentPnt.y) { + nsIWidget* widget = menuPopupFrame->GetWidget(); + if ((aPnt.x != currentPnt.x || aPnt.y != currentPnt.y) || (widget && + widget->GetClientOffset() != menuPopupFrame->GetLastClientOffset())) { // Update the popup's position using SetPopupPosition if the popup is // anchored and at the parent level as these maintain their position // relative to the parent window. Otherwise, just update the popup to // the specified screen coordinates. if (menuPopupFrame->IsAnchored() && menuPopupFrame->PopupLevel() == ePopupLevelParent) { menuPopupFrame->SetPopupPosition(nsnull, true); } @@ -1459,18 +1460,17 @@ nsXULPopupManager::MayShowPopup(nsMenuPo // Don't show popups that we already have in our popup chain if (IsPopupOpen(aPopup->GetContent())) { NS_WARNING("Refusing to show duplicate popup"); return false; } // if the popup was just rolled up, don't reopen it - nsCOMPtr<nsIWidget> widget; - aPopup->GetWidget(getter_AddRefs(widget)); + nsCOMPtr<nsIWidget> widget = aPopup->GetWidget(); if (widget && widget->GetLastRollup() == aPopup->GetContent()) return false; nsCOMPtr<nsISupports> cont = aPopup->PresContext()->GetContainer(); nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont); nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti); if (!baseWin) return false; @@ -1613,18 +1613,17 @@ nsXULPopupManager::SetCaptureState(nsICo if (mWidget) { mWidget->CaptureRollupEvents(this, false, false); mWidget = nsnull; } if (item) { nsMenuPopupFrame* popup = item->Frame(); - nsCOMPtr<nsIWidget> widget; - popup->GetWidget(getter_AddRefs(widget)); + nsCOMPtr<nsIWidget> widget = popup->GetWidget(); if (widget) { widget->CaptureRollupEvents(this, true, popup->ConsumeOutsideClicks()); mWidget = widget; popup->AttachedDismissalListener(); } } UpdateKeyboardListeners();
--- a/layout/xul/test/browser_bug703210.js +++ b/layout/xul/test/browser_bug703210.js @@ -2,17 +2,17 @@ function test() { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); let doStopPropagation = function (aEvent) { aEvent.stopPropagation(); } - let onPopupShowing = function (aEvent) + let onPopupShown = function (aEvent) { is(aEvent.originalTarget.localName, "tooltip", "tooltip is showing"); let doc = gBrowser.contentDocument; let win = gBrowser.contentWindow; let p2 = doc.getElementById("p2"); setTimeout(function () { EventUtils.synthesizeMouseAtCenter(p2, { type: "mousemove" }, win); }, 0); @@ -24,17 +24,17 @@ function test() { let doc = gBrowser.contentDocument; doc.removeEventListener("mousemove", doStopPropagation, true); doc.removeEventListener("mouseenter", doStopPropagation, true); doc.removeEventListener("mouseleave", doStopPropagation, true); doc.removeEventListener("mouseover", doStopPropagation, true); doc.removeEventListener("mouseout", doStopPropagation, true); - document.removeEventListener("popupshowing", onPopupShowing, true); + document.removeEventListener("popupshown", onPopupShown, true); document.removeEventListener("popuphiding", onPopupHiding, true); gBrowser.removeCurrentTab(); finish(); } let onLoad = function (aEvent) { @@ -45,17 +45,17 @@ function test() { EventUtils.synthesizeMouseAtCenter(p2, { type: "mousemove" }, win); doc.addEventListener("mousemove", doStopPropagation, true); doc.addEventListener("mouseenter", doStopPropagation, true); doc.addEventListener("mouseleave", doStopPropagation, true); doc.addEventListener("mouseover", doStopPropagation, true); doc.addEventListener("mouseout", doStopPropagation, true); - document.addEventListener("popupshown", onPopupShowing, true); + document.addEventListener("popupshown", onPopupShown, true); document.addEventListener("popuphiding", onPopupHiding, true); EventUtils.synthesizeMouseAtCenter(p1, { type: "mousemove" }, win); } gBrowser.selectedBrowser.addEventListener("load", function () { setTimeout(onLoad, 0); }, true);
--- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -70,20 +70,21 @@ # define MOZ_HAVE_CXX11_DELETE # endif # endif #elif defined(__GNUC__) # if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L # if __GNUC__ > 4 # define MOZ_HAVE_CXX11_DELETE # define MOZ_HAVE_CXX11_OVERRIDE -# define MOZ_HAVE CXX11_FINAL final +# define MOZ_HAVE_CXX11_FINAL final # elif __GNUC__ == 4 # if __GNUC_MINOR__ >= 7 # define MOZ_HAVE_CXX11_OVERRIDE +# define MOZ_HAVE_CXX11_FINAL final # endif # if __GNUC_MINOR__ >= 4 # define MOZ_HAVE_CXX11_DELETE # endif # endif # else /* __final is a non-C++11 GCC synonym for 'final', per GCC r176655. */ # if __GNUC__ > 4
--- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -782,16 +782,23 @@ pref("network.http.qos", 0); // to wait before trying a different connection. 0 means do not use a second // connection. pref("network.http.connection-retry-timeout", 250); // Disable IPv6 for backup connections to workaround problems about broken // IPv6 connectivity. pref("network.http.fast-fallback-to-IPv4", true); +// Try and use SPDY when using SSL +pref("network.http.spdy.enabled", false); +pref("network.http.spdy.chunk-size", 4096); +pref("network.http.spdy.timeout", 180); +pref("network.http.spdy.coalesce-hostnames", true); +pref("network.http.spdy.use-alternate-protocol", true); + // default values for FTP // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594, // Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22) // per Section 4.7 "Low-Latency Data Service Class". pref("network.ftp.data.qos", 0); pref("network.ftp.control.qos", 0); // </http>
--- a/netwerk/base/src/nsSocketTransport2.cpp +++ b/netwerk/base/src/nsSocketTransport2.cpp @@ -547,17 +547,18 @@ nsSocketOutputStream::Flush() NS_IMETHODIMP nsSocketOutputStream::Write(const char *buf, PRUint32 count, PRUint32 *countWritten) { SOCKET_LOG(("nsSocketOutputStream::Write [this=%x count=%u]\n", this, count)); *countWritten = 0; - if (count == 0) + // A write of 0 bytes can be used to force the initial SSL handshake. + if (count == 0 && mByteCount) return NS_OK; PRFileDesc *fd; { MutexAutoLock lock(mTransport->mLock); if (NS_FAILED(mCondition)) return mCondition;
--- a/netwerk/base/src/nsSocketTransportService2.cpp +++ b/netwerk/base/src/nsSocketTransportService2.cpp @@ -49,16 +49,24 @@ #include "plstr.h" #include "nsIPrefService.h" #include "nsIPrefBranch2.h" #include "nsServiceManagerUtils.h" #include "nsIOService.h" #include "mozilla/FunctionTimer.h" +// XXX: There is no good header file to put these in. :( +namespace mozilla { namespace psm { + +void InitializeSSLServerCertVerificationThreads(); +void StopSSLServerCertVerificationThreads(); + +} } // namespace mozilla::psm + using namespace mozilla; #if defined(PR_LOGGING) PRLogModuleInfo *gSocketTransportLog = nsnull; #endif nsSocketTransportService *gSocketTransportService = nsnull; PRThread *gSocketThread = nsnull; @@ -604,16 +612,18 @@ nsSocketTransportService::AfterProcessNe return NS_OK; } NS_IMETHODIMP nsSocketTransportService::Run() { SOCKET_LOG(("STS thread init\n")); + psm::InitializeSSLServerCertVerificationThreads(); + gSocketThread = PR_GetCurrentThread(); // add thread event to poll list (mThreadEvent may be NULL) mPollList[0].fd = mThreadEvent; mPollList[0].in_flags = PR_POLL_READ; mPollList[0].out_flags = 0; nsIThread *thread = NS_GetCurrentThread(); @@ -660,16 +670,18 @@ nsSocketTransportService::Run() DetachSocket(mIdleList, &mIdleList[i]); // Final pass over the event queue. This makes sure that events posted by // socket detach handlers get processed. NS_ProcessPendingEvents(thread); gSocketThread = nsnull; + psm::StopSSLServerCertVerificationThreads(); + SOCKET_LOG(("STS thread exit\n")); return NS_OK; } nsresult nsSocketTransportService::DoPollIteration(bool wait) { SOCKET_LOG(("STS poll iter [%d]\n", wait));
--- a/netwerk/base/src/nsStandardURL.cpp +++ b/netwerk/base/src/nsStandardURL.cpp @@ -516,29 +516,30 @@ nsStandardURL::BuildNormalizedSpec(const // spec length. // // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref] PRUint32 approxLen = 0; // the scheme is already ASCII if (mScheme.mLen > 0) - approxLen += mScheme.mLen + 3; // includes room for "://"; + approxLen += mScheme.mLen + 3; // includes room for "://", which we insert always // encode URL segments; convert UTF-8 to origin charset and possibly escape. // results written to encXXX variables only if |spec| is not already in the // appropriate encoding. { GET_SEGMENT_ENCODER(encoder); GET_QUERY_ENCODER(queryEncoder); // Items using an extraLen of 1 don't add anything unless mLen > 0 // Username@ approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username, encUsername, useEncUsername, 1); - // :Password - approxLen += encoder.EncodeSegmentCount(spec, mPassword, esc_Password, encPassword, useEncPassword, 1); + // :password - we insert the ':' even if there's no actual password if "user:@" was in the spec + if (mPassword.mLen >= 0) + approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword, esc_Password, encPassword, useEncPassword); // mHost is handled differently below due to encoding differences NS_ABORT_IF_FALSE(mPort > 0 || mPort == -1, "Invalid negative mPort"); if (mPort != -1 && mPort != mDefaultPort) { // :port portbuf.AppendInt(mPort); approxLen += portbuf.Length() + 1; } @@ -558,16 +559,17 @@ nsStandardURL::BuildNormalizedSpec(const if (mRef.mLen >= 0) approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref, encRef, useEncRef); } // do not escape the hostname, if IPv6 address literal, mHost will // already point to a [ ] delimited IPv6 address literal. // However, perform Unicode normalization on it, as IDN does. mHostEncoding = eEncoding_ASCII; + // Note that we don't disallow URLs without a host - file:, etc if (mHost.mLen > 0) { const nsCSubstring& tempHost = Substring(spec + mHost.mPos, spec + mHost.mPos + mHost.mLen); if (tempHost.FindChar('\0') != kNotFound) return NS_ERROR_MALFORMED_URI; // null embedded in hostname if (tempHost.FindChar(' ') != kNotFound) return NS_ERROR_MALFORMED_URI; // don't allow spaces in the hostname if ((useEncHost = NormalizeIDN(tempHost, encHost)))
--- a/netwerk/cache/nsCacheService.cpp +++ b/netwerk/cache/nsCacheService.cpp @@ -692,17 +692,17 @@ nsCacheProfilePrefObserver::ReadPrefs(ns bool same; if (NS_SUCCEEDED(profDir->Equals(directory, &same)) && !same) { // We no longer store the cache directory in the main // profile directory, so we should cleanup the old one. rv = profDir->AppendNative(NS_LITERAL_CSTRING("Cache")); if (NS_SUCCEEDED(rv)) { bool exists; if (NS_SUCCEEDED(profDir->Exists(&exists)) && exists) - DeleteDir(profDir, false, false); + nsDeleteDir::DeleteDir(profDir, false); } } } } // use file cache in build tree only if asked, to avoid cache dir litter if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) { rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, getter_AddRefs(directory)); @@ -1033,16 +1033,21 @@ nsCacheService::Init() CACHE_LOG_INIT(); nsresult rv = NS_NewThread(getter_AddRefs(mCacheIOThread)); if (NS_FAILED(rv)) { NS_WARNING("Can't create cache IO thread"); } + rv = nsDeleteDir::Init(); + if (NS_FAILED(rv)) { + NS_WARNING("Can't initialize nsDeleteDir"); + } + // initialize hashtable for active cache entries rv = mActiveEntries.Init(); if (NS_FAILED(rv)) return rv; // create profile/preference observer mObserver = new nsCacheProfilePrefObserver(); if (!mObserver) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(mObserver); @@ -1056,16 +1061,17 @@ nsCacheService::Init() return NS_OK; } void nsCacheService::Shutdown() { nsCOMPtr<nsIThread> cacheIOThread; + Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer; { nsCacheServiceAutoLock lock; NS_ASSERTION(mInitialized, "can't shutdown nsCacheService unless it has been initialized."); if (mInitialized) { @@ -1101,16 +1107,39 @@ nsCacheService::Shutdown() #endif mCacheIOThread.swap(cacheIOThread); } } // lock if (cacheIOThread) cacheIOThread->Shutdown(); + + bool finishDeleting = false; + nsresult rv; + nsCOMPtr<nsIPrefBranch2> branch = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!branch) { + NS_WARNING("Failed to get pref service!"); + } else { + bool isSet; + rv = branch->GetBoolPref("privacy.sanitize.sanitizeOnShutdown", &isSet); + if (NS_SUCCEEDED(rv) && isSet) { + rv = branch->GetBoolPref("privacy.clearOnShutdown.cache", &isSet); + if (NS_SUCCEEDED(rv) && isSet) { + finishDeleting = true; + } + } + } + if (finishDeleting) { + Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer; + nsDeleteDir::Shutdown(finishDeleting); + } else { + Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer; + nsDeleteDir::Shutdown(finishDeleting); + } } nsresult nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult) { nsresult rv;
--- a/netwerk/cache/nsDeleteDir.cpp +++ b/netwerk/cache/nsDeleteDir.cpp @@ -17,16 +17,17 @@ * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Darin Fisher <darin@meer.net> * Jason Duell <jduell.mcbugs@gmail.com> + * Michal Novotny <michal.novotny@gmail.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -35,110 +36,427 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsDeleteDir.h" #include "nsIFile.h" #include "nsString.h" -#include "prthread.h" #include "mozilla/Telemetry.h" #include "nsITimer.h" +#include "nsISimpleEnumerator.h" +#include "nsAutoPtr.h" +#include "nsThreadUtils.h" +#include "nsISupportsPriority.h" using namespace mozilla; -static void DeleteDirThreadFunc(void *arg) +class nsBlockOnBackgroundThreadEvent : public nsRunnable { +public: + nsBlockOnBackgroundThreadEvent() {} + NS_IMETHOD Run() + { + MutexAutoLock lock(nsDeleteDir::gInstance->mLock); + nsDeleteDir::gInstance->mCondVar.Notify(); + return NS_OK; + } +}; + +class nsDestroyThreadEvent : public nsRunnable { +public: + nsDestroyThreadEvent(nsIThread *thread) + : mThread(thread) + {} + NS_IMETHOD Run() + { + mThread->Shutdown(); + return NS_OK; + } +private: + nsCOMPtr<nsIThread> mThread; +}; + + +nsDeleteDir * nsDeleteDir::gInstance = nsnull; + +nsDeleteDir::nsDeleteDir() + : mLock("nsDeleteDir.mLock"), + mCondVar(mLock, "nsDeleteDir.mCondVar"), + mShutdownPending(false), + mStopDeleting(false) { - Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer; - nsIFile *dir = static_cast<nsIFile *>(arg); - dir->Remove(true); - NS_RELEASE(dir); + NS_ASSERTION(gInstance==nsnull, "multiple nsCacheService instances!"); +} + +nsDeleteDir::~nsDeleteDir() +{ + gInstance = nsnull; +} + +nsresult +nsDeleteDir::Init() +{ + if (gInstance) + return NS_ERROR_ALREADY_INITIALIZED; + + gInstance = new nsDeleteDir(); + return NS_OK; } -static void CreateDeleterThread(nsITimer *aTimer, void *arg) +nsresult +nsDeleteDir::Shutdown(bool finishDeleting) { - nsIFile *dir = static_cast<nsIFile *>(arg); + if (!gInstance) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMArray<nsIFile> dirsToRemove; + nsCOMPtr<nsIThread> thread; + { + MutexAutoLock lock(gInstance->mLock); + NS_ASSERTION(!gInstance->mShutdownPending, + "Unexpected state in nsDeleteDir::Shutdown()"); + gInstance->mShutdownPending = true; + + if (!finishDeleting) + gInstance->mStopDeleting = true; + + // remove all pending timers + for (PRInt32 i = gInstance->mTimers.Count(); i > 0; i--) { + nsCOMPtr<nsITimer> timer = gInstance->mTimers[i-1]; + gInstance->mTimers.RemoveObjectAt(i-1); + timer->Cancel(); + + nsCOMArray<nsIFile> *arg; + timer->GetClosure((reinterpret_cast<void**>(&arg))); - // create the worker thread - PR_CreateThread(PR_USER_THREAD, DeleteDirThreadFunc, dir, PR_PRIORITY_LOW, - PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0); + if (finishDeleting) + dirsToRemove.AppendObjects(*arg); + + // delete argument passed to the timer + delete arg; + } + + thread.swap(gInstance->mThread); + if (thread) { + // dispatch event and wait for it to run and notify us, so we know thread + // has completed all work and can be shutdown + nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent(); + nsresult rv = thread->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Failed dispatching block-event"); + return NS_ERROR_UNEXPECTED; + } + + rv = gInstance->mCondVar.Wait(); + thread->Shutdown(); + } + } + + delete gInstance; + + for (PRInt32 i = 0; i < dirsToRemove.Count(); i++) + dirsToRemove[i]->Remove(true); + + return NS_OK; } -nsresult DeleteDir(nsIFile *dirIn, bool moveToTrash, bool sync, - PRUint32 delay) +nsresult +nsDeleteDir::InitThread() +{ + if (mThread) + return NS_OK; + + nsresult rv = NS_NewThread(getter_AddRefs(mThread)); + if (NS_FAILED(rv)) { + NS_WARNING("Can't create background thread"); + return rv; + } + + nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread); + if (p) { + p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST); + } + return NS_OK; +} + +void +nsDeleteDir::DestroyThread() +{ + if (!mThread) + return; + + if (mTimers.Count()) + // more work to do, so don't delete thread. + return; + + NS_DispatchToMainThread(new nsDestroyThreadEvent(mThread)); + mThread = nsnull; +} + +void +nsDeleteDir::TimerCallback(nsITimer *aTimer, void *arg) +{ + Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer; + { + MutexAutoLock lock(gInstance->mLock); + + PRInt32 idx = gInstance->mTimers.IndexOf(aTimer); + if (idx == -1) { + // Timer was canceled and removed during shutdown. + return; + } + + gInstance->mTimers.RemoveObjectAt(idx); + } + + nsAutoPtr<nsCOMArray<nsIFile> > dirList; + dirList = static_cast<nsCOMArray<nsIFile> *>(arg); + + bool shuttingDown = false; + for (PRInt32 i = 0; i < dirList->Count() && !shuttingDown; i++) { + gInstance->RemoveDir((*dirList)[i], &shuttingDown); + } + + { + MutexAutoLock lock(gInstance->mLock); + gInstance->DestroyThread(); + } +} + +nsresult +nsDeleteDir::DeleteDir(nsIFile *dirIn, bool moveToTrash, PRUint32 delay) { Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer; + + if (!gInstance) + return NS_ERROR_NOT_INITIALIZED; + nsresult rv; nsCOMPtr<nsIFile> trash, dir; // Need to make a clone of this since we don't want to modify the input // file object. rv = dirIn->Clone(getter_AddRefs(dir)); if (NS_FAILED(rv)) return rv; if (moveToTrash) { rv = GetTrashDir(dir, &trash); if (NS_FAILED(rv)) return rv; - nsCAutoString leaf; - rv = trash->GetNativeLeafName(leaf); + nsCAutoString origLeaf; + rv = trash->GetNativeLeafName(origLeaf); if (NS_FAILED(rv)) return rv; // Important: must rename directory w/o changing parent directory: else on // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file // tree: was hanging GUI for *minutes* on large cache dirs. + // Append random number to the trash directory and check if it exists. + nsCAutoString leaf; + for (PRInt32 i = 0; i < 10; i++) { + leaf = origLeaf; + leaf.AppendInt(rand()); + rv = trash->SetNativeLeafName(leaf); + if (NS_FAILED(rv)) + return rv; + + bool exists; + if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) { + break; + } + + leaf.Truncate(); + } + + // Fail if we didn't find unused trash directory within the limit + if (!leaf.Length()) + return NS_ERROR_FAILURE; + rv = dir->MoveToNative(nsnull, leaf); - if (NS_FAILED(rv)) { - nsresult rvMove = rv; - // TrashDir may already exist (if we crashed while deleting it, etc.) - // In that case current Cache dir should be small--just move it to - // subdirectory of TrashDir and eat the NTFS ACL overhead. - leaf.AppendInt(rand()); // support this happening multiple times - rv = dir->MoveToNative(trash, leaf); - if (NS_FAILED(rv)) - return rvMove; - // Be paranoid and delete immediately if we're seeing old trash when - // we're creating a new one - delay = 0; - } + if (NS_FAILED(rv)) + return rv; } else { // we want to pass a clone of the original off to the worker thread. trash.swap(dir); } - // Steal ownership of trash directory; let the thread release it. - nsIFile *trashRef = nsnull; - trash.swap(trashRef); + nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>); + arg->AppendObject(trash); - if (sync) { - DeleteDirThreadFunc(trashRef); - } else { - if (delay) { - nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv); - if (NS_FAILED(rv)) - return NS_ERROR_UNEXPECTED; - timer->InitWithFuncCallback(CreateDeleterThread, trashRef, delay, - nsITimer::TYPE_ONE_SHOT); - } else { - CreateDeleterThread(nsnull, trashRef); - } - } + rv = gInstance->PostTimer(arg, delay); + if (NS_FAILED(rv)) + return rv; + arg.forget(); return NS_OK; } -nsresult GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result) +nsresult +nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result) { nsresult rv = target->Clone(getter_AddRefs(*result)); if (NS_FAILED(rv)) return rv; nsCAutoString leaf; rv = (*result)->GetNativeLeafName(leaf); if (NS_FAILED(rv)) return rv; leaf.AppendLiteral(".Trash"); return (*result)->SetNativeLeafName(leaf); } + +nsresult +nsDeleteDir::RemoveOldTrashes(nsIFile *cacheDir) +{ + if (!gInstance) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + + static bool firstRun = true; + + if (!firstRun) + return NS_OK; + + firstRun = false; + + nsCOMPtr<nsIFile> trash; + rv = GetTrashDir(cacheDir, &trash); + if (NS_FAILED(rv)) + return rv; + + nsAutoString trashName; + rv = trash->GetLeafName(trashName); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIFile> parent; + rv = cacheDir->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsISimpleEnumerator> iter; + rv = parent->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) + return rv; + + bool more; + nsCOMPtr<nsISupports> elem; + nsAutoPtr<nsCOMArray<nsIFile> > dirList; + + while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) { + rv = iter->GetNext(getter_AddRefs(elem)); + if (NS_FAILED(rv)) + continue; + + nsCOMPtr<nsIFile> file = do_QueryInterface(elem); + if (!file) + continue; + + nsAutoString leafName; + rv = file->GetLeafName(leafName); + if (NS_FAILED(rv)) + continue; + + // match all names that begin with the trash name (i.e. "Cache.Trash") + if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) { + if (!dirList) + dirList = new nsCOMArray<nsIFile>; + dirList->AppendObject(file); + } + } + + if (dirList) { + rv = gInstance->PostTimer(dirList, 90000); + if (NS_FAILED(rv)) + return rv; + + dirList.forget(); + } + + return NS_OK; +} + +nsresult +nsDeleteDir::PostTimer(void *arg, PRUint32 delay) +{ + nsresult rv; + + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + + MutexAutoLock lock(mLock); + + rv = InitThread(); + if (NS_FAILED(rv)) + return rv; + + rv = timer->SetTarget(mThread); + if (NS_FAILED(rv)) + return rv; + + rv = timer->InitWithFuncCallback(TimerCallback, arg, delay, + nsITimer::TYPE_ONE_SHOT); + if (NS_FAILED(rv)) + return rv; + + mTimers.AppendObject(timer); + return NS_OK; +} + +nsresult +nsDeleteDir::RemoveDir(nsIFile *file, bool *stopDeleting) +{ + nsresult rv; + bool isLink; + + rv = file->IsSymlink(&isLink); + if (NS_FAILED(rv) || isLink) + return NS_ERROR_UNEXPECTED; + + bool isDir; + rv = file->IsDirectory(&isDir); + if (NS_FAILED(rv)) + return rv; + + if (isDir) { + nsCOMPtr<nsISimpleEnumerator> iter; + rv = file->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) + return rv; + + bool more; + nsCOMPtr<nsISupports> elem; + while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) { + rv = iter->GetNext(getter_AddRefs(elem)); + if (NS_FAILED(rv)) { + NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir"); + continue; + } + + nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem); + if (!file2) { + NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir"); + continue; + } + + RemoveDir(file2, stopDeleting); + // No check for errors to remove as much as possible + + if (*stopDeleting) + return NS_OK; + } + } + + file->Remove(false); + // No check for errors to remove as much as possible + + MutexAutoLock lock(mLock); + if (mStopDeleting) + *stopDeleting = true; + + return NS_OK; +}
--- a/netwerk/cache/nsDeleteDir.h +++ b/netwerk/cache/nsDeleteDir.h @@ -16,16 +16,17 @@ * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Darin Fisher <darin@meer.net> + * Michal Novotny <michal.novotny@gmail.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -35,44 +36,77 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef nsDeleteDir_h__ #define nsDeleteDir_h__ #include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "mozilla/Mutex.h" +#include "mozilla/CondVar.h" class nsIFile; +class nsIThread; +class nsITimer; -/** - * This routine attempts to delete a directory that may contain some files that - * are still in use. This later point is only an issue on Windows and a few - * other systems. - * - * If the moveToTrash parameter is true, then the process for deleting the - * directory creates a sibling directory of the same name with the ".Trash" - * suffix. It then attempts to move the given directory into the corresponding - * trash folder (moving individual files if necessary). Next, it proceeds to - * delete each file in the trash folder on a low-priority background thread. - * - * If the moveToTrash parameter is false, then the given directory is deleted - * directly. - * - * If the sync flag is true, then the delete operation runs to completion - * before this function returns. Otherwise, deletion occurs asynchronously. - * - * If 'delay' is non-zero, the directory will not be deleted until the - * specified number of milliseconds have passed. (The directory is still - * renamed immediately if 'moveToTrash' is passed, so upon return it is safe - * to create a directory with the same name). This parameter is ignored if - * 'sync' is true. - */ -NS_HIDDEN_(nsresult) DeleteDir(nsIFile *dir, bool moveToTrash, bool sync, - PRUint32 delay = 0); + +class nsDeleteDir { +public: + nsDeleteDir(); + ~nsDeleteDir(); + + static nsresult Init(); + static nsresult Shutdown(bool finishDeleting); + + /** + * This routine attempts to delete a directory that may contain some files + * that are still in use. This latter point is only an issue on Windows and + * a few other systems. + * + * If the moveToTrash parameter is true we first rename the given directory + * "foo.Trash123" (where "foo" is the original directory name, and "123" is + * a random number, in order to support multiple concurrent deletes). The + * directory is then deleted, file-by-file, on a background thread. + * + * If the moveToTrash parameter is false, then the given directory is deleted + * directly. + * + * If 'delay' is non-zero, the directory will not be deleted until the + * specified number of milliseconds have passed. (The directory is still + * renamed immediately if 'moveToTrash' is passed, so upon return it is safe + * to create a directory with the same name). + */ + static nsresult DeleteDir(nsIFile *dir, bool moveToTrash, PRUint32 delay = 0); -/** - * This routine returns the trash directory corresponding to the given - * directory. - */ -NS_HIDDEN_(nsresult) GetTrashDir(nsIFile *dir, nsCOMPtr<nsIFile> *result); + /** + * Returns the trash directory corresponding to the given directory. + */ + static nsresult GetTrashDir(nsIFile *dir, nsCOMPtr<nsIFile> *result); + + /** + * Remove all trashes left from previous run. This function does nothing when + * called second and more times. + */ + static nsresult RemoveOldTrashes(nsIFile *cacheDir); + + static void TimerCallback(nsITimer *aTimer, void *arg); + +private: + friend class nsBlockOnBackgroundThreadEvent; + friend class nsDestroyThreadEvent; + + nsresult InitThread(); + void DestroyThread(); + nsresult PostTimer(void *arg, PRUint32 delay); + nsresult RemoveDir(nsIFile *file, bool *stopDeleting); + + static nsDeleteDir * gInstance; + mozilla::Mutex mLock; + mozilla::CondVar mCondVar; + nsCOMArray<nsITimer> mTimers; + nsCOMPtr<nsIThread> mThread; + bool mShutdownPending; + bool mStopDeleting; +}; #endif // nsDeleteDir_h__
--- a/netwerk/cache/nsDiskCacheDevice.cpp +++ b/netwerk/cache/nsDiskCacheDevice.cpp @@ -414,17 +414,19 @@ nsDiskCacheDevice::Init() } if (!mCacheDirectory) return NS_ERROR_FAILURE; rv = mBindery.Init(); if (NS_FAILED(rv)) return rv; - + + nsDeleteDir::RemoveOldTrashes(mCacheDirectory); + // Open Disk Cache rv = OpenDiskCache(); if (NS_FAILED(rv)) { (void) mCacheMap.Close(false); return rv; } mInitialized = true; @@ -439,27 +441,16 @@ nsresult nsDiskCacheDevice::Shutdown() { nsCacheService::AssertOwnsLock(); nsresult rv = Shutdown_Private(true); if (NS_FAILED(rv)) return rv; - if (mCacheDirectory) { - // delete any trash files left-over before shutting down. - nsCOMPtr<nsIFile> trashDir; - GetTrashDir(mCacheDirectory, &trashDir); - if (trashDir) { - bool exists; - if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists) - DeleteDir(trashDir, false, true); - } - } - return NS_OK; } nsresult nsDiskCacheDevice::Shutdown_Private(bool flush) { CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush)); @@ -999,28 +990,26 @@ nsDiskCacheDevice::OpenDiskCache() { Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer; // if we don't have a cache directory, create one and open it bool exists; nsresult rv = mCacheDirectory->Exists(&exists); if (NS_FAILED(rv)) return rv; - bool trashing = false; if (exists) { // Try opening cache map file. rv = mCacheMap.Open(mCacheDirectory); // move "corrupt" caches to trash if (rv == NS_ERROR_FILE_CORRUPTED) { // delay delete by 1 minute to avoid IO thrash at startup - rv = DeleteDir(mCacheDirectory, true, false, 60000); + rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000); if (NS_FAILED(rv)) return rv; exists = false; - trashing = true; } else if (NS_FAILED(rv)) return rv; } // if we don't have a cache directory, create one and open it if (!exists) { rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777); @@ -1030,46 +1019,33 @@ nsDiskCacheDevice::OpenDiskCache() return rv; // reopen the cache map rv = mCacheMap.Open(mCacheDirectory); if (NS_FAILED(rv)) return rv; } - if (!trashing) { - // delete any trash files leftover from a previous run - nsCOMPtr<nsIFile> trashDir; - GetTrashDir(mCacheDirectory, &trashDir); - if (trashDir) { - bool exists; - if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists) { - // be paranoid and delete immediately if leftover - DeleteDir(trashDir, false, false); - } - } - } - return NS_OK; } nsresult nsDiskCacheDevice::ClearDiskCache() { if (mBindery.ActiveBindings()) return NS_ERROR_CACHE_IN_USE; nsresult rv = Shutdown_Private(false); // false: don't bother flushing if (NS_FAILED(rv)) return rv; // If the disk cache directory is already gone, then it's not an error if // we fail to delete it ;-) - rv = DeleteDir(mCacheDirectory, true, false); + rv = nsDeleteDir::DeleteDir(mCacheDirectory, true); if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) return rv; return Init(); } nsresult
--- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -75,16 +75,17 @@ HttpBaseChannel::HttpBaseChannel() , mForceAllowThirdPartyCookie(false) , mUploadStreamHasHeaders(false) , mInheritApplicationCache(true) , mChooseApplicationCache(false) , mLoadedFromApplicationCache(false) , mChannelIsForDownload(false) , mTracingEnabled(true) , mTimingEnabled(false) + , mAllowSpdy(true) , mSuspendCount(0) , mRedirectedCachekeys(nsnull) { LOG(("Creating HttpBaseChannel @%x\n", this)); // grab a reference to the handler to ensure that it doesn't go away. NS_ADDREF(gHttpHandler); @@ -1305,16 +1306,32 @@ HttpBaseChannel::HTTPUpgrade(const nsACS NS_ENSURE_ARG(!aProtocolName.IsEmpty()); NS_ENSURE_ARG_POINTER(aListener); mUpgradeProtocol = aProtocolName; mUpgradeProtocolCallback = aListener; return NS_OK; } +NS_IMETHODIMP +HttpBaseChannel::GetAllowSpdy(bool *aAllowSpdy) +{ + NS_ENSURE_ARG_POINTER(aAllowSpdy); + + *aAllowSpdy = mAllowSpdy; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy) +{ + mAllowSpdy = aAllowSpdy; + return NS_OK; +} + //----------------------------------------------------------------------------- // HttpBaseChannel::nsISupportsPriority //----------------------------------------------------------------------------- NS_IMETHODIMP HttpBaseChannel::GetPriority(PRInt32 *value) { *value = mPriority; @@ -1614,16 +1631,18 @@ HttpBaseChannel::SetupReplacementChannel httpChannel->SetAllowPipelining(mAllowPipelining); // convey the new redirection limit httpChannel->SetRedirectionLimit(mRedirectionLimit - 1); nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel); if (httpInternal) { // convey the mForceAllowThirdPartyCookie flag httpInternal->SetForceAllowThirdPartyCookie(mForceAllowThirdPartyCookie); + // convey the spdy flag + httpInternal->SetAllowSpdy(mAllowSpdy); // update the DocumentURI indicator since we are being redirected. // if this was a top-level document channel, then the new channel // should have its mDocumentURI point to newURI; otherwise, we // just need to pass along our mDocumentURI to the new channel. if (newURI && (mURI == mDocumentURI)) httpInternal->SetDocumentURI(newURI); else
--- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -162,16 +162,19 @@ public: NS_IMETHOD GetCanceled(bool *aCanceled); NS_IMETHOD GetChannelIsForDownload(bool *aChannelIsForDownload); NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload); NS_IMETHOD SetCacheKeysRedirectChain(nsTArray<nsCString> *cacheKeys); NS_IMETHOD GetLocalAddress(nsACString& addr); NS_IMETHOD GetLocalPort(PRInt32* port); NS_IMETHOD GetRemoteAddress(nsACString& addr); NS_IMETHOD GetRemotePort(PRInt32* port); + NS_IMETHOD GetAllowSpdy(bool *aAllowSpdy); + NS_IMETHOD SetAllowSpdy(bool aAllowSpdy); + inline void CleanRedirectCacheChainIfNecessary() { if (mRedirectedCachekeys) { delete mRedirectedCachekeys; mRedirectedCachekeys = nsnull; } } NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName, @@ -290,16 +293,17 @@ protected: PRUint32 mUploadStreamHasHeaders : 1; PRUint32 mInheritApplicationCache : 1; PRUint32 mChooseApplicationCache : 1; PRUint32 mLoadedFromApplicationCache : 1; PRUint32 mChannelIsForDownload : 1; PRUint32 mTracingEnabled : 1; // True if timing collection is enabled PRUint32 mTimingEnabled : 1; + PRUint32 mAllowSpdy : 1; // Current suspension depth for this channel object PRUint32 mSuspendCount; nsTArray<nsCString> *mRedirectedCachekeys; }; // Share some code while working around C++'s absurd inability to handle casting
--- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -1074,17 +1074,17 @@ HttpChannelChild::AsyncOpen(nsIStreamLis SendAsyncOpen(IPC::URI(mURI), IPC::URI(mOriginalURI), IPC::URI(mDocumentURI), IPC::URI(mReferrer), mLoadFlags, mRequestHeaders, mRequestHead.Method(), IPC::InputStream(mUploadStream), mUploadStreamHasHeaders, mPriority, mRedirectionLimit, mAllowPipelining, mForceAllowThirdPartyCookie, mSendResumeAt, mStartPos, mEntityID, mChooseApplicationCache, - appCacheClientId); + appCacheClientId, mAllowSpdy); return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIHttpChannel //-----------------------------------------------------------------------------
--- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -138,17 +138,18 @@ HttpChannelParent::RecvAsyncOpen(const I const PRUint16& priority, const PRUint8& redirectionLimit, const bool& allowPipelining, const bool& forceAllowThirdPartyCookie, const bool& doResumeAt, const PRUint64& startPos, const nsCString& entityID, const bool& chooseApplicationCache, - const nsCString& appCacheClientID) + const nsCString& appCacheClientID, + const bool& allowSpdy) { nsCOMPtr<nsIURI> uri(aURI); nsCOMPtr<nsIURI> originalUri(aOriginalURI); nsCOMPtr<nsIURI> docUri(aDocURI); nsCOMPtr<nsIURI> referrerUri(aReferrerURI); nsCString uriSpec; uri->GetSpec(uriSpec); @@ -198,16 +199,17 @@ HttpChannelParent::RecvAsyncOpen(const I httpChan->SetUploadStreamHasHeaders(uploadStreamHasHeaders); } if (priority != nsISupportsPriority::PRIORITY_NORMAL) httpChan->SetPriority(priority); httpChan->SetRedirectionLimit(redirectionLimit); httpChan->SetAllowPipelining(allowPipelining); httpChan->SetForceAllowThirdPartyCookie(forceAllowThirdPartyCookie); + httpChan->SetAllowSpdy(allowSpdy); nsCOMPtr<nsIApplicationCacheChannel> appCacheChan = do_QueryInterface(mChannel); nsCOMPtr<nsIApplicationCacheService> appCacheService = do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID); bool setChooseApplicationCache = chooseApplicationCache; if (appCacheChan && appCacheService) {
--- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -92,17 +92,18 @@ protected: const PRUint16& priority, const PRUint8& redirectionLimit, const bool& allowPipelining, const bool& forceAllowThirdPartyCookie, const bool& doResumeAt, const PRUint64& startPos, const nsCString& entityID, const bool& chooseApplicationCache, - const nsCString& appCacheClientID); + const nsCString& appCacheClientID, + const bool& allowSpdy); virtual bool RecvConnectChannel(const PRUint32& channelId); virtual bool RecvSetPriority(const PRUint16& priority); virtual bool RecvSetCacheTokenCachedCharset(const nsCString& charset); virtual bool RecvSuspend(); virtual bool RecvResume(); virtual bool RecvCancel(const nsresult& status); virtual bool RecvRedirect2Verify(const nsresult& result,
--- a/netwerk/protocol/http/Makefile.in +++ b/netwerk/protocol/http/Makefile.in @@ -105,16 +105,18 @@ CPPSRCS = \ HttpBaseChannel.cpp \ nsHttpChannel.cpp \ nsHttpPipeline.cpp \ nsHttpActivityDistributor.cpp \ nsHttpChannelAuthProvider.cpp \ HttpChannelParent.cpp \ HttpChannelChild.cpp \ HttpChannelParentListener.cpp \ + SpdySession.cpp \ + SpdyStream.cpp \ $(NULL) LOCAL_INCLUDES = \ -I$(srcdir)/../../base/src \ -I$(topsrcdir)/xpcom/ds \ -I$(topsrcdir)/content/base/src \ -I$(topsrcdir)/content/events/src \ $(NULL)
--- a/netwerk/protocol/http/PHttpChannel.ipdl +++ b/netwerk/protocol/http/PHttpChannel.ipdl @@ -76,17 +76,18 @@ parent: PRUint16 priority, PRUint8 redirectionLimit, bool allowPipelining, bool forceAllowThirdPartyCookie, bool resumeAt, PRUint64 startPos, nsCString entityID, bool chooseApplicationCache, - nsCString appCacheClientID); + nsCString appCacheClientID, + bool allowSpdy); // Used to connect redirected-to channel on the parent with redirected-to // channel on the child. ConnectChannel(PRUint32 channelId); SetPriority(PRUint16 priority); SetCacheTokenCachedCharset(nsCString charset);
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/http/SpdySession.cpp @@ -0,0 +1,1861 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus <mcmanus@ducksong.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsHttp.h" +#include "SpdySession.h" +#include "SpdyStream.h" +#include "nsHttpConnection.h" +#include "prnetdb.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Preferences.h" +#include "prprf.h" + +#ifdef DEBUG +// defined by the socket transport service while active +extern PRThread *gSocketThread; +#endif + +namespace mozilla { +namespace net { + +// SpdySession has multiple inheritance of things that implement +// nsISupports, so this magic is taken from nsHttpPipeline that +// implements some of the same abstract classes. +NS_IMPL_THREADSAFE_ADDREF(SpdySession) +NS_IMPL_THREADSAFE_RELEASE(SpdySession) +NS_INTERFACE_MAP_BEGIN(SpdySession) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection) +NS_INTERFACE_MAP_END + +SpdySession::SpdySession(nsAHttpTransaction *aHttpTransaction, + nsISocketTransport *aSocketTransport, + PRInt32 firstPriority) + : mSocketTransport(aSocketTransport), + mSegmentReader(nsnull), + mSegmentWriter(nsnull), + mSendingChunkSize(kSendingChunkSize), + mNextStreamID(1), + mConcurrentHighWater(0), + mDownstreamState(BUFFERING_FRAME_HEADER), + mPartialFrame(nsnull), + mFrameBufferSize(kDefaultBufferSize), + mFrameBufferUsed(0), + mFrameDataLast(false), + mFrameDataStream(nsnull), + mNeedsCleanup(nsnull), + mDecompressBufferSize(kDefaultBufferSize), + mDecompressBufferUsed(0), + mShouldGoAway(false), + mClosed(false), + mCleanShutdown(false), + mGoAwayID(0), + mMaxConcurrent(kDefaultMaxConcurrent), + mConcurrent(0), + mServerPushedResources(0), + mOutputQueueSize(kDefaultQueueSize), + mOutputQueueUsed(0), + mOutputQueueSent(0) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + LOG3(("SpdySession::SpdySession %p transaction 1 = %p", + this, aHttpTransaction)); + + mStreamIDHash.Init(); + mStreamTransactionHash.Init(); + mConnection = aHttpTransaction->Connection(); + mFrameBuffer = new char[mFrameBufferSize]; + mDecompressBuffer = new char[mDecompressBufferSize]; + mOutputQueueBuffer = new char[mOutputQueueSize]; + zlibInit(); + + mSendingChunkSize = + Preferences::GetInt("network.http.spdy.chunk-size", kSendingChunkSize); + AddStream(aHttpTransaction, firstPriority); +} + +PLDHashOperator +SpdySession::Shutdown(nsAHttpTransaction *key, + nsAutoPtr<SpdyStream> &stream, + void *closure) +{ + SpdySession *self = static_cast<SpdySession *>(closure); + + if (self->mCleanShutdown && + self->mGoAwayID < stream->StreamID()) + stream->Close(NS_ERROR_NET_RESET); // can be restarted + else + stream->Close(NS_ERROR_ABORT); + + return PL_DHASH_NEXT; +} + +SpdySession::~SpdySession() +{ + LOG3(("SpdySession::~SpdySession %p", this)); + + inflateEnd(&mDownstreamZlib); + deflateEnd(&mUpstreamZlib); + + mStreamTransactionHash.Enumerate(Shutdown, this); + Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater); + Telemetry::Accumulate(Telemetry::SPDY_TOTAL_STREAMS, (mNextStreamID - 1) / 2); + Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS, + mServerPushedResources); +} + +void +SpdySession::LogIO(SpdySession *self, SpdyStream *stream, const char *label, + const char *data, PRUint32 datalen) +{ + if (!LOG4_ENABLED()) + return; + + LOG4(("SpdySession::LogIO %p stream=%p id=0x%X [%s]", + self, stream, stream ? stream->StreamID() : 0, label)); + + // Max line is (16 * 3) + 10(prefix) + newline + null + char linebuf[128]; + PRUint32 index; + char *line = linebuf; + + linebuf[127] = 0; + + for (index = 0; index < datalen; ++index) { + if (!(index % 16)) { + if (index) { + *line = 0; + LOG4(("%s", linebuf)); + } + line = linebuf; + PR_snprintf(line, 128, "%08X: ", index); + line += 10; + } + PR_snprintf(line, 128 - (line - linebuf), "%02X ", + ((unsigned char *)data)[index]); + line += 3; + } + if (index) { + *line = 0; + LOG4(("%s", linebuf)); + } +} + +typedef nsresult (*Control_FX) (SpdySession *self); +static Control_FX sControlFunctions[] = +{ + nsnull, + SpdySession::HandleSynStream, + SpdySession::HandleSynReply, + SpdySession::HandleRstStream, + SpdySession::HandleSettings, + SpdySession::HandleNoop, + SpdySession::HandlePing, + SpdySession::HandleGoAway, + SpdySession::HandleHeaders, + SpdySession::HandleWindowUpdate +}; + +bool +SpdySession::RoomForMoreConcurrent() +{ + return (mConcurrent < mMaxConcurrent); +} + +bool +SpdySession::RoomForMoreStreams() +{ + if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) + return false; + + return !mShouldGoAway; +} + +PRUint32 +SpdySession::RegisterStreamID(SpdyStream *stream) +{ + LOG3(("SpdySession::RegisterStreamID session=%p stream=%p id=0x%X " + "concurrent=%d",this, stream, mNextStreamID, mConcurrent)); + + NS_ABORT_IF_FALSE(mNextStreamID < 0xfffffff0, + "should have stopped admitting streams"); + + PRUint32 result = mNextStreamID; + mNextStreamID += 2; + + // We've used up plenty of ID's on this session. Start + // moving to a new one before there is a crunch involving + // server push streams or concurrent non-registered submits + if (mNextStreamID >= kMaxStreamID) + mShouldGoAway = true; + + mStreamIDHash.Put(result, stream); + return result; +} + +bool +SpdySession::AddStream(nsAHttpTransaction *aHttpTransaction, + PRInt32 aPriority) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(!mStreamTransactionHash.Get(aHttpTransaction), + "AddStream duplicate transaction pointer"); + + aHttpTransaction->SetConnection(this); + SpdyStream *stream = new SpdyStream(aHttpTransaction, + this, + mSocketTransport, + mSendingChunkSize, + &mUpstreamZlib, + aPriority); + + + LOG3(("SpdySession::AddStream session=%p stream=%p NextID=0x%X (tentative)", + this, stream, mNextStreamID)); + + mStreamTransactionHash.Put(aHttpTransaction, stream); + + if (RoomForMoreConcurrent()) { + LOG3(("SpdySession::AddStream %p stream %p activated immediately.", + this, stream)); + ActivateStream(stream); + } + else { + LOG3(("SpdySession::AddStream %p stream %p queued.", + this, stream)); + mQueuedStreams.Push(stream); + } + + return true; +} + +void +SpdySession::ActivateStream(SpdyStream *stream) +{ + mConcurrent++; + if (mConcurrent > mConcurrentHighWater) + mConcurrentHighWater = mConcurrent; + LOG3(("SpdySession::AddStream %p activating stream %p Currently %d" + "streams in session, high water mark is %d", + this, stream, mConcurrent, mConcurrentHighWater)); + + mReadyForWrite.Push(stream); + SetWriteCallbacks(stream->Transaction()); + + // Kick off the SYN transmit without waiting for the poll loop + PRUint32 countRead; + ReadSegments(nsnull, kDefaultBufferSize, &countRead); +} + +void +SpdySession::ProcessPending() +{ + while (RoomForMoreConcurrent()) { + SpdyStream *stream = static_cast<SpdyStream *>(mQueuedStreams.PopFront()); + if (!stream) + return; + LOG3(("SpdySession::ProcessPending %p stream %p activated from queue.", + this, stream)); + ActivateStream(stream); + } +} + +void +SpdySession::SetWriteCallbacks(nsAHttpTransaction *aTrans) +{ + if (mConnection && (WriteQueueSize() || mOutputQueueUsed)) + mConnection->ResumeSend(aTrans); +} + +void +SpdySession::FlushOutputQueue() +{ + if (!mSegmentReader || !mOutputQueueUsed) + return; + + nsresult rv; + PRUint32 countRead; + PRUint32 avail = mOutputQueueUsed - mOutputQueueSent; + + rv = mSegmentReader-> + OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail, + &countRead); + LOG3(("SpdySession::FlushOutputQueue %p sz=%d rv=%x actual=%d", + this, avail, rv, countRead)); + + // Dont worry about errors on write, we will pick this up as a read error too + if (NS_FAILED(rv)) + return; + + if (countRead == avail) { + mOutputQueueUsed = 0; + mOutputQueueSent = 0; + return; + } + + mOutputQueueSent += countRead; + if (mOutputQueueSize - mOutputQueueUsed < kQueueTailRoom) { + // The output queue is filling up and we just sent some data out, so + // this is a good time to rearrange the output queue. + + mOutputQueueUsed -= mOutputQueueSent; + memmove(mOutputQueueBuffer.get(), + mOutputQueueBuffer.get() + mOutputQueueSent, + mOutputQueueUsed); + mOutputQueueSent = 0; + } +} + +void +SpdySession::DontReuse() +{ + mShouldGoAway = true; + if(!mStreamTransactionHash.Count()) + Close(NS_OK); +} + +PRUint32 +SpdySession::WriteQueueSize() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + PRUint32 count = mUrgentForWrite.GetSize() + mReadyForWrite.GetSize(); + + if (mPartialFrame) + ++count; + return count; +} + +void +SpdySession::ChangeDownstreamState(enum stateType newState) +{ + LOG3(("SpdyStream::ChangeDownstreamState() %p from %X to %X", + this, mDownstreamState, newState)); + mDownstreamState = newState; + + if (mDownstreamState == BUFFERING_FRAME_HEADER) { + if (mFrameDataLast && mFrameDataStream) { + mFrameDataLast = 0; + if (!mFrameDataStream->RecvdFin()) { + mFrameDataStream->SetRecvdFin(true); + --mConcurrent; + ProcessPending(); + } + } + mFrameBufferUsed = 0; + mFrameDataStream = nsnull; + } + + return; +} + +void +SpdySession::EnsureBuffer(nsAutoArrayPtr<char> &buf, + PRUint32 newSize, + PRUint32 preserve, + PRUint32 &objSize) +{ + if (objSize >= newSize) + return; + + objSize = newSize; + nsAutoArrayPtr<char> tmp(new char[objSize]); + memcpy (tmp, buf, preserve); + buf = tmp; +} + +void +SpdySession::zlibInit() +{ + mDownstreamZlib.zalloc = SpdyStream::zlib_allocator; + mDownstreamZlib.zfree = SpdyStream::zlib_destructor; + mDownstreamZlib.opaque = Z_NULL; + + inflateInit(&mDownstreamZlib); + + mUpstreamZlib.zalloc = SpdyStream::zlib_allocator; + mUpstreamZlib.zfree = SpdyStream::zlib_destructor; + mUpstreamZlib.opaque = Z_NULL; + + deflateInit(&mUpstreamZlib, Z_DEFAULT_COMPRESSION); + deflateSetDictionary(&mUpstreamZlib, + reinterpret_cast<const unsigned char *> + (SpdyStream::kDictionary), + strlen(SpdyStream::kDictionary) + 1); + +} + +nsresult +SpdySession::DownstreamUncompress(char *blockStart, PRUint32 blockLen) +{ + mDecompressBufferUsed = 0; + + mDownstreamZlib.avail_in = blockLen; + mDownstreamZlib.next_in = reinterpret_cast<unsigned char *>(blockStart); + + do { + mDownstreamZlib.next_out = + reinterpret_cast<unsigned char *>(mDecompressBuffer.get()) + + mDecompressBufferUsed; + mDownstreamZlib.avail_out = mDecompressBufferSize - mDecompressBufferUsed; + int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH); + + if (zlib_rv == Z_NEED_DICT) + inflateSetDictionary(&mDownstreamZlib, + reinterpret_cast<const unsigned char *> + (SpdyStream::kDictionary), + strlen(SpdyStream::kDictionary) + 1); + + if (zlib_rv == Z_DATA_ERROR || zlib_rv == Z_MEM_ERROR) + return NS_ERROR_FAILURE; + + mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed - + mDownstreamZlib.avail_out; + + // When there is no more output room, but input still available then + // increase the output space + if (zlib_rv == Z_OK && + !mDownstreamZlib.avail_out && mDownstreamZlib.avail_in) { + LOG3(("SpdySession::DownstreamUncompress %p Large Headers - so far %d", + this, mDecompressBufferSize)); + EnsureBuffer(mDecompressBuffer, + mDecompressBufferSize + 4096, + mDecompressBufferUsed, + mDecompressBufferSize); + } + } + while (mDownstreamZlib.avail_in); + return NS_OK; +} + +nsresult +SpdySession::FindHeader(nsCString name, + nsDependentCSubstring &value) +{ + const unsigned char *nvpair = reinterpret_cast<unsigned char *> + (mDecompressBuffer.get()) + 2; + const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *> + (mDecompressBuffer.get()) + mDecompressBufferUsed; + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 numPairs = + PR_ntohs(reinterpret_cast<PRUint16 *>(mDecompressBuffer.get())[0]); + for (PRUint16 index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 2) + return NS_ERROR_ILLEGAL_VALUE; + PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1]; + if (lastHeaderByte < nvpair + 2 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + nsDependentCSubstring nameString = + Substring (reinterpret_cast<const char *>(nvpair) + 2, + reinterpret_cast<const char *>(nvpair) + 2 + nameLen); + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen]; + if (lastHeaderByte < nvpair + 4 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + if (nameString.Equals(name)) { + value.Assign(((char *)nvpair) + 4 + nameLen, valueLen); + return NS_OK; + } + nvpair += 4 + nameLen + valueLen; + } + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult +SpdySession::ConvertHeaders(nsDependentCSubstring &status, + nsDependentCSubstring &version) +{ + + mFlatHTTPResponseHeaders.Truncate(); + mFlatHTTPResponseHeadersOut = 0; + mFlatHTTPResponseHeaders.SetCapacity(mDecompressBufferUsed + 64); + + // Connection, Keep-Alive and chunked transfer encodings are to be + // removed. + + // Content-Length is 'advisory'.. we will not strip it because it can + // create UI feedback. + + mFlatHTTPResponseHeaders.Append(version); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(" ")); + mFlatHTTPResponseHeaders.Append(status); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); + + const unsigned char *nvpair = reinterpret_cast<unsigned char *> + (mDecompressBuffer.get()) + 2; + const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *> + (mDecompressBuffer.get()) + mDecompressBufferUsed; + + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + + PRUint16 numPairs = + PR_ntohs(reinterpret_cast<PRUint16 *>(mDecompressBuffer.get())[0]); + + for (PRUint16 index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 2) + return NS_ERROR_ILLEGAL_VALUE; + + PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1]; + if (lastHeaderByte < nvpair + 2 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + + nsDependentCSubstring nameString = + Substring (reinterpret_cast<const char *>(nvpair) + 2, + reinterpret_cast<const char *>(nvpair) + 2 + nameLen); + + // a null in the name string is particularly wrong because it will + // break the fix-up-nulls-in-value-string algorithm. + if (nameString.FindChar(0) != -1) + return NS_ERROR_ILLEGAL_VALUE; + + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen]; + if (lastHeaderByte < nvpair + 4 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + + // Look for upper case characters in the name. They are illegal. + for (char *cPtr = nameString.BeginWriting(); + cPtr && cPtr < nameString.EndWriting(); + ++cPtr) { + if (*cPtr <= 'Z' && *cPtr >= 'A') { + nsCString toLog(nameString); + + LOG3(("SpdySession::ConvertHeaders session=%p stream=%p " + "upper case response header found. [%s]\n", + this, mFrameDataStream, toLog.get())); + + return NS_ERROR_ILLEGAL_VALUE; + } + } + + // HTTP Chunked responses are not legal over spdy. We do not need + // to look for chunked specifically because it is the only HTTP + // allowed default encoding and we did not negotiate further encodings + // via TE + if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) { + LOG3(("SpdySession::ConvertHeaders session=%p stream=%p " + "transfer-encoding found. Chunked is invalid and no TE sent.", + this, mFrameDataStream)); + + return NS_ERROR_ILLEGAL_VALUE; + } + + if (!nameString.Equals(NS_LITERAL_CSTRING("version")) && + !nameString.Equals(NS_LITERAL_CSTRING("status")) && + !nameString.Equals(NS_LITERAL_CSTRING("connection")) && + !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { + nsDependentCSubstring valueString = + Substring (reinterpret_cast<const char *>(nvpair) + 4 + nameLen, + reinterpret_cast<const char *>(nvpair) + 4 + nameLen + + valueLen); + + mFlatHTTPResponseHeaders.Append(nameString); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": ")); + + PRInt32 valueIndex; + // NULLs are really "\r\nhdr: " + while ((valueIndex = valueString.FindChar(0)) != -1) { + nsCString replacement = NS_LITERAL_CSTRING("\r\n"); + replacement.Append(nameString); + replacement.Append(NS_LITERAL_CSTRING(": ")); + valueString.Replace(valueIndex, 1, replacement); + } + + mFlatHTTPResponseHeaders.Append(valueString); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); + } + nvpair += 4 + nameLen + valueLen; + } + + mFlatHTTPResponseHeaders.Append( + NS_LITERAL_CSTRING("X-Firefox-Spdy: 1\r\n\r\n")); + LOG (("decoded response headers are:\n%s", + mFlatHTTPResponseHeaders.get())); + + return NS_OK; +} + +void +SpdySession::GeneratePing(PRUint32 aID) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::GeneratePing %p 0x%X\n", this, aID)); + + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 12; + + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[2] = 0; + packet[3] = CONTROL_TYPE_PING; + packet[4] = 0; /* flags */ + packet[5] = 0; + packet[6] = 0; + packet[7] = 4; /* length */ + + aID = PR_htonl(aID); + memcpy (packet + 8, &aID, 4); + + FlushOutputQueue(); +} + +void +SpdySession::GenerateRstStream(PRUint32 aStatusCode, PRUint32 aID) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode)); + + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 16, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 16; + + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[2] = 0; + packet[3] = CONTROL_TYPE_RST_STREAM; + packet[4] = 0; /* flags */ + packet[5] = 0; + packet[6] = 0; + packet[7] = 8; /* length */ + + aID = PR_htonl(aID); + memcpy (packet + 8, &aID, 4); + aStatusCode = PR_htonl(aStatusCode); + memcpy (packet + 12, &aStatusCode, 4); + + FlushOutputQueue(); +} + +void +SpdySession::GenerateGoAway() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::GenerateGoAway %p\n", this)); + + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 12; + + memset (packet, 0, 12); + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[3] = CONTROL_TYPE_GOAWAY; + packet[7] = 4; /* data length */ + + // last-good-stream-id are bytes 8-11, when we accept server push this will + // need to be set non zero + + FlushOutputQueue(); +} + +void +SpdySession::CleanupStream(SpdyStream *aStream, nsresult aResult) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::CleanupStream %p %p 0x%x %X\n", + this, aStream, aStream->StreamID(), aResult)); + + nsresult abortCode = NS_OK; + + if (!aStream->RecvdFin() && aStream->StreamID()) { + LOG3(("Stream had not processed recv FIN, sending RST")); + GenerateRstStream(RST_CANCEL, aStream->StreamID()); + --mConcurrent; + ProcessPending(); + } + + // Check if partial frame writer + if (mPartialFrame == aStream) { + LOG3(("Stream had active partial write frame - need to abort session")); + abortCode = aResult; + if (NS_SUCCEEDED(abortCode)) + abortCode = NS_ERROR_ABORT; + + mPartialFrame = nsnull; + } + + // Check if partial frame reader + if (aStream == mFrameDataStream) { + LOG3(("Stream had active partial read frame on close")); + ChangeDownstreamState(DISCARD_DATA_FRAME); + mFrameDataStream = nsnull; + } + + // check the streams blocked on write, this is linear but the list + // should be pretty short. + PRUint32 size = mReadyForWrite.GetSize(); + for (PRUint32 count = 0; count < size; ++count) { + SpdyStream *stream = static_cast<SpdyStream *>(mReadyForWrite.PopFront()); + if (stream != aStream) + mReadyForWrite.Push(stream); + } + + // Check the streams blocked on urgent (i.e. window update) writing. + // This should also be short. + size = mUrgentForWrite.GetSize(); + for (PRUint32 count = 0; count < size; ++count) { + SpdyStream *stream = static_cast<SpdyStream *>(mUrgentForWrite.PopFront()); + if (stream != aStream) + mUrgentForWrite.Push(stream); + } + + // Remove the stream from the ID hash table. (this one isn't short, which is + // why it is hashed.) + mStreamIDHash.Remove(aStream->StreamID()); + + // Send the stream the close() indication + aStream->Close(aResult); + + // removing from the stream transaction hash will + // delete the SpdyStream and drop the reference to + // its transaction + mStreamTransactionHash.Remove(aStream->Transaction()); + + if (NS_FAILED(abortCode)) + Close(abortCode); + else if (mShouldGoAway && !mStreamTransactionHash.Count()) + Close(NS_OK); +} + +nsresult +SpdySession::HandleSynStream(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM, + "wrong control type"); + + if (self->mFrameDataSize < 12) { + LOG3(("SpdySession::HandleSynStream %p SYN_STREAM too short data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]); + + LOG3(("SpdySession::HandleSynStream %p recv SYN_STREAM (push) for ID 0x%X.", + self, streamID)); + + if (streamID & 0x01) { // test for odd stream ID + LOG3(("SpdySession::HandleSynStream %p recvd SYN_STREAM id must be even.", + self)); + return NS_ERROR_ILLEGAL_VALUE; + } + + ++(self->mServerPushedResources); + + // Anytime we start using the high bit of stream ID (either client or server) + // begin to migrate to a new session. + if (streamID >= kMaxStreamID) + self->mShouldGoAway = true; + + // todo populate cache. For now, just reject server push p3 + self->GenerateRstStream(RST_REFUSED_STREAM, streamID); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleSynReply(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY, + "wrong control type"); + + if (self->mFrameDataSize < 8) { + LOG3(("SpdySession::HandleSynReply %p SYN REPLY too short data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]); + self->mFrameDataStream = self->mStreamIDHash.Get(streamID); + if (!self->mFrameDataStream) { + LOG3(("SpdySession::HandleSynReply %p lookup streamID in syn_reply " + "0x%X failed. NextStreamID = 0x%x", self, streamID, + self->mNextStreamID)); + if (streamID >= self->mNextStreamID) + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + // It is likely that this is a reply to a stream ID that has been canceled. + // For the most part we would like to ignore it, but the header needs to be + // be parsed to keep the compression context synchronized + self->DownstreamUncompress(self->mFrameBuffer + 14, + self->mFrameDataSize - 6); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + if (!self->mFrameDataStream->SetFullyOpen()) { + // "If an endpoint receives multiple SYN_REPLY frames for the same active + // stream ID, it must drop the stream, and send a RST_STREAM for the + // stream with the error PROTOCOL_ERROR." + // + // In addition to that we abort the session - this is a serious protocol + // violation. + + self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->mFrameDataLast = self->mFrameBuffer[4] & kFlag_Data_FIN; + + if (self->mFrameBuffer[4] & kFlag_Data_UNI) { + LOG3(("SynReply had unidirectional flag set on it - nonsensical")); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG3(("SpdySession::HandleSynReply %p SYN_REPLY for 0x%X fin=%d", + self, streamID, self->mFrameDataLast)); + + // The spdystream needs to see flattened http headers + // The Frame Buffer currently holds the complete SYN_REPLY + // frame. The interesting data is at offset 14, where the + // compressed name/value header block lives. + // We unpack that into the mDecompressBuffer - we can't do + // it streamed because the version and status information + // is not guaranteed to be first. This is then finally + // converted to HTTP format in mFlatHTTPResponseHeaders + + nsresult rv = self->DownstreamUncompress(self->mFrameBuffer + 14, + self->mFrameDataSize - 6); + if (NS_FAILED(rv)) + return rv; + + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, + self->mFrameDataSize - 6); + PRUint32 ratio = + (self->mFrameDataSize - 6) * 100 / self->mDecompressBufferUsed; + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); + + // status and version are required. + nsDependentCSubstring status, version; + rv = self->FindHeader(NS_LITERAL_CSTRING("status"), status); + if (NS_FAILED(rv)) + return rv; + + rv = self->FindHeader(NS_LITERAL_CSTRING("version"), version); + if (NS_FAILED(rv)) + return rv; + + rv = self->ConvertHeaders(status, version); + if (NS_FAILED(rv)) + return rv; + + self->ChangeDownstreamState(PROCESSING_CONTROL_SYN_REPLY); + return NS_OK; +} + +nsresult +SpdySession::HandleRstStream(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_RST_STREAM, + "wrong control type"); + + if (self->mFrameDataSize != 8) { + LOG3(("SpdySession::HandleRstStream %p RST_STREAM wrong length data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]); + + self->mDownstreamRstReason = + PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[3]); + + LOG3(("SpdySession::HandleRstStream %p RST_STREAM Reason Code %u ID %x", + self, self->mDownstreamRstReason, streamID)); + + if (self->mDownstreamRstReason == RST_INVALID_STREAM || + self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) { + // basically just ignore this + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + self->mFrameDataStream = self->mStreamIDHash.Get(streamID); + if (!self->mFrameDataStream) { + LOG3(("SpdySession::HandleRstStream %p lookup streamID for RST Frame " + "0x%X failed", self, streamID)); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM); + return NS_OK; +} + +nsresult +SpdySession::HandleSettings(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SETTINGS, + "wrong control type"); + + if (self->mFrameDataSize < 4) { + LOG3(("SpdySession::HandleSettings %p SETTINGS wrong length data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 numEntries = + PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]); + + // Ensure frame is large enough for supplied number of entries + // Each entry is 8 bytes, frame data is reduced by 4 to account for + // the NumEntries value. + if ((self->mFrameDataSize - 4) < (numEntries * 8)) { + LOG3(("SpdySession::HandleSettings %p SETTINGS wrong length data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG3(("SpdySession::HandleSettings %p SETTINGS Control Frame with %d entries", + self, numEntries)); + + for (PRUint32 index = 0; index < numEntries; ++index) { + // To clarify the v2 spec: + // Each entry is a 24 bits of a little endian id + // followed by 8 bits of flags + // followed by a 32 bit big endian value + + unsigned char *setting = reinterpret_cast<unsigned char *> + (self->mFrameBuffer.get()) + 12 + index * 8; + + PRUint32 id = (setting[2] << 16) + (setting[1] << 8) + setting[0]; + PRUint32 flags = setting[3]; + PRUint32 value = PR_ntohl(reinterpret_cast<PRUint32 *>(setting)[1]); + + LOG3(("Settings ID %d, Flags %X, Value %d", id, flags, value)); + + switch (id) + { + case SETTINGS_TYPE_UPLOAD_BW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_UL_BW, value); + break; + + case SETTINGS_TYPE_DOWNLOAD_BW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_DL_BW, value); + break; + + case SETTINGS_TYPE_RTT: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RTT, value); + break; + + case SETTINGS_TYPE_MAX_CONCURRENT: + self->mMaxConcurrent = value; + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value); + break; + + case SETTINGS_TYPE_CWND: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_CWND, value); + break; + + case SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RETRANS, value); + break; + + case SETTINGS_TYPE_INITIAL_WINDOW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10); + break; + + default: + break; + } + + } + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleNoop(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_NOOP, + "wrong control type"); + + if (self->mFrameDataSize != 0) { + LOG3(("SpdySession::HandleNoop %p NOP had data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG3(("SpdySession::HandleNoop %p NOP.", self)); + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandlePing(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_PING, + "wrong control type"); + + if (self->mFrameDataSize != 4) { + LOG3(("SpdySession::HandlePing %p PING had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 pingID = + PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]); + + LOG3(("SpdySession::HandlePing %p PING ID 0x%X.", self, pingID)); + + if (pingID & 0x01) { + // We never expect to see an odd PING beacuse we never generate PING. + // The spec mandates ignoring this + LOG3(("SpdySession::HandlePing %p PING ID from server was odd.", + self)); + } + else { + self->GeneratePing(pingID); + } + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleGoAway(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_GOAWAY, + "wrong control type"); + + if (self->mFrameDataSize != 4) { + LOG3(("SpdySession::HandleGoAway %p GOAWAY had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->mShouldGoAway = true; + self->mGoAwayID = + PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]); + self->mCleanShutdown = true; + + LOG3(("SpdySession::HandleGoAway %p GOAWAY Last-Good-ID 0x%X.", + self, self->mGoAwayID)); + self->ResumeRecv(self); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleHeaders(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_HEADERS, + "wrong control type"); + + if (self->mFrameDataSize < 10) { + LOG3(("SpdySession::HandleHeaders %p HEADERS had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]); + + // this is actually not legal in the HTTP mapping of SPDY. All + // headers are in the syn or syn reply. Log and ignore it. + + LOG3(("SpdySession::HandleHeaders %p HEADERS for Stream 0x%X. " + "They are ignored in the HTTP/SPDY mapping.", + self, streamID)); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleWindowUpdate(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE, + "wrong control type"); + LOG3(("SpdySession::HandleWindowUpdate %p WINDOW UPDATE was " + "received. WINDOW UPDATE is no longer defined in v2. Ignoring.", + self)); + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +// Used for the hashtable enumeration to propogate OnTransportStatus events +struct transportStatus +{ + nsITransport *transport; + nsresult status; + PRUint64 progress; +}; + +static PLDHashOperator +StreamTransportStatus(nsAHttpTransaction *key, + nsAutoPtr<SpdyStream> &stream, + void *closure) +{ + struct transportStatus *status = + static_cast<struct transportStatus *>(closure); + + stream->Transaction()->OnTransportStatus(status->transport, + status->status, + status->progress); + return PL_DHASH_NEXT; +} + + +//----------------------------------------------------------------------------- +// nsAHttpTransaction. It is expected that nsHttpConnection is the caller +// of these methods +//----------------------------------------------------------------------------- + +void +SpdySession::OnTransportStatus(nsITransport* aTransport, + nsresult aStatus, + PRUint64 aProgress) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // nsHttpChannel synthesizes progress events in OnDataAvailable + if (aStatus == nsISocketTransport::STATUS_RECEIVING_FROM) + return; + + // STATUS_SENDING_TO is handled by SpdyStream + if (aStatus == nsISocketTransport::STATUS_SENDING_TO) + return; + + struct transportStatus status; + + status.transport = aTransport; + status.status = aStatus; + status.progress = aProgress; + + mStreamTransactionHash.Enumerate(StreamTransportStatus, &status); +} + +// ReadSegments() is used to write data to the network. Generally, HTTP +// request data is pulled from the approriate transaction and +// converted to SPDY data. Sometimes control data like window-update are +// generated instead. + +nsresult +SpdySession::ReadSegments(nsAHttpSegmentReader *reader, + PRUint32 count, + PRUint32 *countRead) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + *countRead = 0; + + // First priority goes to frames that were writing to the network but were + // blocked part way through. Then to frames that have no streams (e.g ping + // reply) and then third to streams marked urgent (generally they have + // window updates), and finally to streams generally + // ready to send data frames (http requests). + + LOG3(("SpdySession::ReadSegments %p partial frame stream=%p", + this, mPartialFrame)); + + SpdyStream *stream = mPartialFrame; + mPartialFrame = nsnull; + + if (!stream) + stream = static_cast<SpdyStream *>(mUrgentForWrite.PopFront()); + if (!stream) + stream = static_cast<SpdyStream *>(mReadyForWrite.PopFront()); + if (!stream) { + LOG3(("SpdySession %p could not identify a stream to write; suspending.", + this)); + FlushOutputQueue(); + SetWriteCallbacks(nsnull); + return NS_BASE_STREAM_WOULD_BLOCK; + } + + LOG3(("SpdySession %p will write from SpdyStream %p", this, stream)); + + NS_ABORT_IF_FALSE(!mSegmentReader || !reader || (mSegmentReader == reader), + "Inconsistent Write Function Callback"); + + if (reader) + mSegmentReader = reader; + rv = stream->ReadSegments(this, count, countRead); + + FlushOutputQueue(); + + if (stream->BlockedOnWrite()) { + + // We are writing a frame out, but it is blocked on the output stream. + // Make sure to service that stream next write because we can only + // multiplex between complete frames. + + LOG3(("SpdySession::ReadSegments %p dealing with block on write", this)); + + NS_ABORT_IF_FALSE(!mPartialFrame, "partial frame should be empty"); + + mPartialFrame = stream; + SetWriteCallbacks(stream->Transaction()); + return rv; + } + + if (stream->RequestBlockedOnRead()) { + + // We are blocked waiting for input - either more http headers or + // any request body data. When more data from the request stream + // becomes available the httptransaction will call conn->ResumeSend(). + + LOG3(("SpdySession::ReadSegments %p dealing with block on read", this)); + + // call readsegments again if there are other streams ready + // to run in this session + if (WriteQueueSize()) + rv = NS_OK; + else + rv = NS_BASE_STREAM_WOULD_BLOCK; + SetWriteCallbacks(stream->Transaction()); + return rv; + } + + NS_ABORT_IF_FALSE(rv != NS_BASE_STREAM_WOULD_BLOCK, + "Stream Would Block inconsistency"); + + if (NS_FAILED(rv)) { + LOG3(("SpdySession::ReadSegments %p returning FAIL code %X", + this, rv)); + return rv; + } + + if (*countRead > 0) { + LOG3(("SpdySession::ReadSegments %p stream=%p generated end of frame %d", + this, stream, *countRead)); + mReadyForWrite.Push(stream); + SetWriteCallbacks(stream->Transaction()); + return rv; + } + + LOG3(("SpdySession::ReadSegments %p stream=%p stream send complete", + this, stream)); + + // in normal http this is done by nshttpconnection, but that class does not + // know which http transaction has made this state transition. + stream->Transaction()-> + OnTransportStatus(mSocketTransport, nsISocketTransport::STATUS_WAITING_FOR, + LL_ZERO); + /* we now want to recv data */ + mConnection->ResumeRecv(stream->Transaction()); + + // call readsegments again if there are other streams ready + // to go in this session + SetWriteCallbacks(stream->Transaction()); + + return rv; +} + +// WriteSegments() is used to read data off the socket. Generally this is +// just the SPDY frame header and from there the appropriate SPDYStream +// is identified from the Stream-ID. The http transaction associated with +// that read then pulls in the data directly, which it will feed to +// OnWriteSegment(). That function will gateway it into http and feed +// it to the appropriate transaction. + +// we call writer->OnWriteSegment to get a spdy header.. and decide if it is +// data or control.. if it is control, just deal with it. +// if it is data, identify the spdy stream +// call stream->WriteSegemnts which can call this::OnWriteSegment to get the +// data. It always gets full frames if they are part of the stream + +nsresult +SpdySession::WriteSegments(nsAHttpSegmentWriter *writer, + PRUint32 count, + PRUint32 *countWritten) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + *countWritten = 0; + + if (mClosed) + return NS_ERROR_FAILURE; + + SetWriteCallbacks(nsnull); + + // We buffer all control frames and act on them in this layer. + // We buffer the first 8 bytes of data frames (the header) but + // the actual data is passed through unprocessed. + + if (mDownstreamState == BUFFERING_FRAME_HEADER) { + // The first 8 bytes of every frame is header information that + // we are going to want to strip before passing to http. That is + // true of both control and data packets. + + NS_ABORT_IF_FALSE(mFrameBufferUsed < 8, + "Frame Buffer Used Too Large for State"); + + rv = writer->OnWriteSegment(mFrameBuffer + mFrameBufferUsed, + 8 - mFrameBufferUsed, + countWritten); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + ResumeRecv(nsnull); + } + return rv; + } + + LogIO(this, nsnull, "Reading Frame Header", + mFrameBuffer + mFrameBufferUsed, *countWritten); + + mFrameBufferUsed += *countWritten; + + if (mFrameBufferUsed < 8) + { + LOG3(("SpdySession::WriteSegments %p " + "BUFFERING FRAME HEADER incomplete size=%d", + this, mFrameBufferUsed)); + return rv; + } + + // For both control and data frames the second 32 bit word of the header + // is 8-flags, 24-length. (network byte order) + mFrameDataSize = + PR_ntohl(reinterpret_cast<PRUint32 *>(mFrameBuffer.get())[1]); + mFrameDataSize &= 0x00ffffff; + mFrameDataRead = 0; + + if (mFrameBuffer[0] & kFlag_Control) { + EnsureBuffer(mFrameBuffer, mFrameDataSize + 8, 8, mFrameBufferSize); + ChangeDownstreamState(BUFFERING_CONTROL_FRAME); + + // The first 32 bit word of the header is + // 1 ctrl - 15 version - 16 type + PRUint16 version = + PR_ntohs(reinterpret_cast<PRUint16 *>(mFrameBuffer.get())[0]); + version &= 0x7fff; + + mFrameControlType = + PR_ntohs(reinterpret_cast<PRUint16 *>(mFrameBuffer.get())[1]); + + LOG3(("SpdySession::WriteSegments %p - Control Frame Identified " + "type %d version %d data len %d", + this, mFrameControlType, version, mFrameDataSize)); + + if (mFrameControlType >= CONTROL_TYPE_LAST || + mFrameControlType <= CONTROL_TYPE_FIRST) + return NS_ERROR_ILLEGAL_VALUE; + + // The protocol document says this value must be 1 even though this + // is known as version 2.. Testing interop indicates that is a typo + // in the protocol document + if (version != 2) { + return NS_ERROR_ILLEGAL_VALUE; + } + } + else { + ChangeDownstreamState(PROCESSING_DATA_FRAME); + + PRUint32 streamID = + PR_ntohl(reinterpret_cast<PRUint32 *>(mFrameBuffer.get())[0]); + mFrameDataStream = mStreamIDHash.Get(streamID); + if (!mFrameDataStream) { + LOG3(("SpdySession::WriteSegments %p lookup streamID 0x%X failed. " + "Next = 0x%x", this, streamID, mNextStreamID)); + if (streamID >= mNextStreamID) + GenerateRstStream(RST_INVALID_STREAM, streamID); + ChangeDownstreamState(DISCARD_DATA_FRAME); + } + mFrameDataLast = (mFrameBuffer[4] & kFlag_Data_FIN); + Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, mFrameDataSize >> 10); + LOG3(("Start Processing Data Frame. " + "Session=%p Stream ID 0x%x Stream Ptr %p Fin=%d Len=%d", + this, streamID, mFrameDataStream, mFrameDataLast, mFrameDataSize)); + + if (mFrameBuffer[4] & kFlag_Data_ZLIB) { + LOG3(("Data flag has ZLIB flag set which is not valid >=2 spdy")); + return NS_ERROR_ILLEGAL_VALUE; + } + } + } + + if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) { + if (mDownstreamRstReason == RST_REFUSED_STREAM) + rv = NS_ERROR_NET_RESET; //we can retry this 100% safely + else if (mDownstreamRstReason == RST_CANCEL || + mDownstreamRstReason == RST_PROTOCOL_ERROR || + mDownstreamRstReason == RST_INTERNAL_ERROR || + mDownstreamRstReason == RST_UNSUPPORTED_VERSION) + rv = NS_ERROR_NET_INTERRUPT; + else + rv = NS_ERROR_ILLEGAL_VALUE; + + if (mDownstreamRstReason != RST_REFUSED_STREAM && + mDownstreamRstReason != RST_CANCEL) + mShouldGoAway = true; + + // mFrameDataStream is reset by ChangeDownstreamState + SpdyStream *stream = mFrameDataStream; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + CleanupStream(stream, rv); + return NS_OK; + } + + if (mDownstreamState == PROCESSING_DATA_FRAME || + mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) { + + mSegmentWriter = writer; + rv = mFrameDataStream->WriteSegments(this, count, countWritten); + mSegmentWriter = nsnull; + + if (rv == NS_BASE_STREAM_CLOSED) { + // This will happen when the transaction figures out it is EOF, generally + // due to a content-length match being made + SpdyStream *stream = mFrameDataStream; + if (mFrameDataRead == mFrameDataSize) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + CleanupStream(stream, NS_OK); + NS_ABORT_IF_FALSE(!mNeedsCleanup, "double cleanup out of data frame"); + return NS_OK; + } + + if (mNeedsCleanup) { + CleanupStream(mNeedsCleanup, NS_OK); + mNeedsCleanup = nsnull; + } + + // In v3 this is where we would generate a window update + + return rv; + } + + if (mDownstreamState == DISCARD_DATA_FRAME) { + char trash[4096]; + PRUint32 count = NS_MIN(4096U, mFrameDataSize - mFrameDataRead); + + if (!count) { + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + *countWritten = 1; + return NS_OK; + } + + rv = writer->OnWriteSegment(trash, count, countWritten); + + if (NS_FAILED(rv)) { + // maybe just blocked reading from network + ResumeRecv(nsnull); + return rv; + } + + LogIO(this, nsnull, "Discarding Frame", trash, *countWritten); + + mFrameDataRead += *countWritten; + + if (mFrameDataRead == mFrameDataSize) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return rv; + } + + NS_ABORT_IF_FALSE(mDownstreamState == BUFFERING_CONTROL_FRAME, + "Not in Bufering Control Frame State"); + NS_ABORT_IF_FALSE(mFrameBufferUsed == 8, + "Frame Buffer Header Not Present"); + + rv = writer->OnWriteSegment(mFrameBuffer + 8 + mFrameDataRead, + mFrameDataSize - mFrameDataRead, + countWritten); + if (NS_FAILED(rv)) { + // maybe just blocked reading from network + ResumeRecv(nsnull); + return rv; + } + + LogIO(this, nsnull, "Reading Control Frame", + mFrameBuffer + 8 + mFrameDataRead, *countWritten); + + mFrameDataRead += *countWritten; + + if (mFrameDataRead != mFrameDataSize) + return NS_OK; + + rv = sControlFunctions[mFrameControlType](this); + + NS_ABORT_IF_FALSE(NS_FAILED(rv) || + mDownstreamState != BUFFERING_CONTROL_FRAME, + "Control Handler returned OK but did not change state"); + + if (mShouldGoAway && !mStreamTransactionHash.Count()) + Close(NS_OK); + return rv; +} + +void +SpdySession::Close(nsresult aReason) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (mClosed) + return; + + LOG3(("SpdySession::Close %p %X", this, aReason)); + + mClosed = true; + mStreamTransactionHash.Enumerate(Shutdown, this); + GenerateGoAway(); + mConnection = nsnull; +} + +void +SpdySession::CloseTransaction(nsAHttpTransaction *aTransaction, + nsresult aResult) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::CloseTransaction %p %p %x", this, aTransaction, aResult)); + + // Generally this arrives as a cancel event from the connection manager. + + // need to find the stream and call CleanupStream() on it. + SpdyStream *stream = mStreamTransactionHash.Get(aTransaction); + if (!stream) { + LOG3(("SpdySession::CloseTransaction %p %p %x - not found.", + this, aTransaction, aResult)); + return; + } + LOG3(("SpdySession::CloseTranscation probably a cancel. " + "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p", + this, aTransaction, aResult, stream->StreamID(), stream)); + CleanupStream(stream, aResult); + ResumeRecv(this); +} + + +//----------------------------------------------------------------------------- +// nsAHttpSegmentReader +//----------------------------------------------------------------------------- + +nsresult +SpdySession::OnReadSegment(const char *buf, + PRUint32 count, + PRUint32 *countRead) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + + if (!mOutputQueueUsed && mSegmentReader) { + + // try and write directly without output queue + rv = mSegmentReader->OnReadSegment(buf, count, countRead); + if (NS_SUCCEEDED(rv) || (rv != NS_BASE_STREAM_WOULD_BLOCK)) + return rv; + } + + if (mOutputQueueUsed + count > mOutputQueueSize) + FlushOutputQueue(); + + if (mOutputQueueUsed + count > mOutputQueueSize) + count = mOutputQueueSize - mOutputQueueUsed; + + if (!count) + return NS_BASE_STREAM_WOULD_BLOCK; + + memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count); + mOutputQueueUsed += count; + *countRead = count; + + FlushOutputQueue(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentWriter +//----------------------------------------------------------------------------- + +nsresult +SpdySession::OnWriteSegment(char *buf, + PRUint32 count, + PRUint32 *countWritten) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter"); + nsresult rv; + + if (mDownstreamState == PROCESSING_DATA_FRAME) { + + if (mFrameDataLast && + mFrameDataRead == mFrameDataSize) { + // This will result in Close() being called + mNeedsCleanup = mFrameDataStream; + + LOG3(("SpdySession::OnWriteSegment %p - recorded downstream fin of " + "stream %p 0x%X", this, mFrameDataStream, + mFrameDataStream->StreamID())); + *countWritten = 0; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_BASE_STREAM_CLOSED; + } + + count = NS_MIN(count, mFrameDataSize - mFrameDataRead); + rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten); + if (NS_FAILED(rv)) + return rv; + + LogIO(this, mFrameDataStream, "Reading Data Frame", buf, *countWritten); + + mFrameDataRead += *countWritten; + + if ((mFrameDataRead == mFrameDataSize) && !mFrameDataLast) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + + return rv; + } + + if (mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) { + + if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && + mFrameDataLast) { + *countWritten = 0; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_BASE_STREAM_CLOSED; + } + + count = NS_MIN(count, + mFlatHTTPResponseHeaders.Length() - + mFlatHTTPResponseHeadersOut); + memcpy(buf, + mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut, + count); + mFlatHTTPResponseHeadersOut += count; + *countWritten = count; + + if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && + !mFrameDataLast) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; +} + +//----------------------------------------------------------------------------- +// Modified methods of nsAHttpConnection +//----------------------------------------------------------------------------- + +nsresult +SpdySession::ResumeSend(nsAHttpTransaction *caller) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::ResumeSend %p caller=%p", this, caller)); + + // a trapped signal from the http transaction to the connection that + // it is no longer blocked on read. + + if (!mConnection) + return NS_ERROR_FAILURE; + + SpdyStream *stream = mStreamTransactionHash.Get(caller); + if (stream) + mReadyForWrite.Push(stream); + else + LOG3(("SpdySession::ResumeSend %p caller %p not found", this, caller)); + + return mConnection->ResumeSend(caller); +} + +nsresult +SpdySession::ResumeRecv(nsAHttpTransaction *caller) +{ + if (!mConnection) + return NS_ERROR_FAILURE; + + return mConnection->ResumeRecv(caller); +} + +bool +SpdySession::IsPersistent() +{ + return PR_TRUE; +} + +nsresult +SpdySession::TakeTransport(nsISocketTransport **, + nsIAsyncInputStream **, + nsIAsyncOutputStream **) +{ + NS_ABORT_IF_FALSE(false, "TakeTransport of SpdySession"); + return NS_ERROR_UNEXPECTED; +} + +nsHttpConnection * +SpdySession::TakeHttpConnection() +{ + NS_ABORT_IF_FALSE(false, "TakeHttpConnection of SpdySession"); + return nsnull; +} + +nsISocketTransport * +SpdySession::Transport() +{ + if (!mConnection) + return nsnull; + return mConnection->Transport(); +} + +//----------------------------------------------------------------------------- +// unused methods of nsAHttpTransaction +// We can be sure of this because SpdySession is only constructed in +// nsHttpConnection and is never passed out of that object +//----------------------------------------------------------------------------- + +void +SpdySession::SetConnection(nsAHttpConnection *) +{ + // This is unexpected + NS_ABORT_IF_FALSE(false, "SpdySession::SetConnection()"); +} + +void +SpdySession::GetSecurityCallbacks(nsIInterfaceRequestor **, + nsIEventTarget **) +{ + // This is unexpected + NS_ABORT_IF_FALSE(false, "SpdySession::GetSecurityCallbacks()"); +} + +void +SpdySession::SetSSLConnectFailed() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::SetSSLConnectFailed()"); +} + +bool +SpdySession::IsDone() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::IsDone()"); + return PR_FALSE; +} + +nsresult +SpdySession::Status() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::Status()"); + return NS_ERROR_UNEXPECTED; +} + +PRUint32 +SpdySession::Available() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::Available()"); + return 0; +} + +nsHttpRequestHead * +SpdySession::RequestHead() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(false, + "SpdySession::RequestHead() " + "should not be called after SPDY is setup"); + return NULL; +} + +//----------------------------------------------------------------------------- +// Pass through methods of nsAHttpConnection +//----------------------------------------------------------------------------- + +nsAHttpConnection * +SpdySession::Connection() +{ + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + return mConnection; +} + +nsresult +SpdySession::OnHeadersAvailable(nsAHttpTransaction *transaction, + nsHttpRequestHead *requestHead, + nsHttpResponseHead *responseHead, + bool *reset) +{ + return mConnection->OnHeadersAvailable(transaction, + requestHead, + responseHead, + reset); +} + +void +SpdySession::GetConnectionInfo(nsHttpConnectionInfo **connInfo) +{ + mConnection->GetConnectionInfo(connInfo); +} + +void +SpdySession::GetSecurityInfo(nsISupports **supports) +{ + mConnection->GetSecurityInfo(supports); +} + +bool +SpdySession::IsReused() +{ + return mConnection->IsReused(); +} + +nsresult +SpdySession::PushBack(const char *buf, PRUint32 len) +{ + return mConnection->PushBack(buf, len); +} + +bool +SpdySession::LastTransactionExpectedNoContent() +{ + return mConnection->LastTransactionExpectedNoContent(); +} + +void +SpdySession::SetLastTransactionExpectedNoContent(bool val) +{ + mConnection->SetLastTransactionExpectedNoContent(val); +} + +} // namespace mozilla::net +} // namespace mozilla +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/http/SpdySession.h @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus <mcmanus@ducksong.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_net_SpdySession_h +#define mozilla_net_SpdySession_h + +// SPDY as defined by +// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 + +#include "nsAHttpTransaction.h" +#include "nsAHttpConnection.h" +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "nsDeque.h" +#include "nsHashKeys.h" +#include "zlib.h" + +class nsHttpConnection; +class nsISocketTransport; + +namespace mozilla { namespace net { + +class SpdyStream; + +class SpdySession : public nsAHttpTransaction + , public nsAHttpConnection + , public nsAHttpSegmentReader + , public nsAHttpSegmentWriter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSAHTTPTRANSACTION + NS_DECL_NSAHTTPCONNECTION + NS_DECL_NSAHTTPSEGMENTREADER + NS_DECL_NSAHTTPSEGMENTWRITER + + SpdySession(nsAHttpTransaction *, nsISocketTransport *, PRInt32); + ~SpdySession(); + + bool AddStream(nsAHttpTransaction *, PRInt32); + bool CanReuse() { return !mShouldGoAway && !mClosed; } + void DontReuse(); + bool RoomForMoreStreams(); + PRUint32 RegisterStreamID(SpdyStream *); + + const static PRUint8 kFlag_Control = 0x80; + + const static PRUint8 kFlag_Data_FIN = 0x01; + const static PRUint8 kFlag_Data_UNI = 0x02; + const static PRUint8 kFlag_Data_ZLIB = 0x02; + + const static PRUint8 kPri00 = 0x00; + const static PRUint8 kPri01 = 0x40; + const static PRUint8 kPri02 = 0x80; + const static PRUint8 kPri03 = 0xC0; + + enum + { + CONTROL_TYPE_FIRST = 0, + CONTROL_TYPE_SYN_STREAM = 1, + CONTROL_TYPE_SYN_REPLY = 2, + CONTROL_TYPE_RST_STREAM = 3, + CONTROL_TYPE_SETTINGS = 4, + CONTROL_TYPE_NOOP = 5, + CONTROL_TYPE_PING = 6, + CONTROL_TYPE_GOAWAY = 7, + CONTROL_TYPE_HEADERS = 8, + CONTROL_TYPE_WINDOW_UPDATE = 9, /* no longer in v2 */ + CONTROL_TYPE_LAST = 10 + }; + + enum + { + RST_PROTOCOL_ERROR = 1, + RST_INVALID_STREAM = 2, + RST_REFUSED_STREAM = 3, + RST_UNSUPPORTED_VERSION = 4, + RST_CANCEL = 5, + RST_INTERNAL_ERROR = 6, + RST_FLOW_CONTROL_ERROR = 7, + RST_BAD_ASSOC_STREAM = 8 + }; + + enum + { + SETTINGS_TYPE_UPLOAD_BW = 1, // kb/s + SETTINGS_TYPE_DOWNLOAD_BW = 2, // kb/s + SETTINGS_TYPE_RTT = 3, // ms + SETTINGS_TYPE_MAX_CONCURRENT = 4, // streams + SETTINGS_TYPE_CWND = 5, // packets + SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage + SETTINGS_TYPE_INITIAL_WINDOW = 7 // bytes. Not used in v2. + }; + + // This should be big enough to hold all of your control packets, + // but if it needs to grow for huge headers it can do so dynamically. + // About 1% of requests to SPDY google services seem to be > 1000 + // with all less than 2000. + const static PRUint32 kDefaultBufferSize = 2000; + + const static PRUint32 kDefaultQueueSize = 16000; + const static PRUint32 kQueueTailRoom = 4000; + const static PRUint32 kSendingChunkSize = 4000; + const static PRUint32 kDefaultMaxConcurrent = 100; + const static PRUint32 kMaxStreamID = 0x7800000; + + static nsresult HandleSynStream(SpdySession *); + static nsresult HandleSynReply(SpdySession *); + static nsresult HandleRstStream(SpdySession *); + static nsresult HandleSettings(SpdySession *); + static nsresult HandleNoop(SpdySession *); + static nsresult HandlePing(SpdySession *); + static nsresult HandleGoAway(SpdySession *); + static nsresult HandleHeaders(SpdySession *); + static nsresult HandleWindowUpdate(SpdySession *); + + static void EnsureBuffer(nsAutoArrayPtr<char> &, + PRUint32, PRUint32, PRUint32 &); + + // For writing the SPDY data stream to LOG4 + static void LogIO(SpdySession *, SpdyStream *, const char *, + const char *, PRUint32); + +private: + + enum stateType { + BUFFERING_FRAME_HEADER, + BUFFERING_CONTROL_FRAME, + PROCESSING_DATA_FRAME, + DISCARD_DATA_FRAME, + PROCESSING_CONTROL_SYN_REPLY, + PROCESSING_CONTROL_RST_STREAM + }; + + PRUint32 WriteQueueSize(); + void ChangeDownstreamState(enum stateType); + nsresult DownstreamUncompress(char *, PRUint32); + void zlibInit(); + nsresult FindHeader(nsCString, nsDependentCSubstring &); + nsresult ConvertHeaders(nsDependentCSubstring &, + nsDependentCSubstring &); + void GeneratePing(PRUint32); + void GenerateRstStream(PRUint32, PRUint32); + void GenerateGoAway(); + void CleanupStream(SpdyStream *, nsresult); + + void SetWriteCallbacks(nsAHttpTransaction *); + void FlushOutputQueue(); + + bool RoomForMoreConcurrent(); + void ActivateStream(SpdyStream *); + void ProcessPending(); + + static PLDHashOperator Shutdown(nsAHttpTransaction *, + nsAutoPtr<SpdyStream> &, + void *); + + // This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken + // from the first transcation on this session. That object contains the + // pointer to the real network-level nsHttpConnection object. + nsRefPtr<nsAHttpConnection> mConnection; + + // The underlying socket transport object is needed to propogate some events + nsISocketTransport *mSocketTransport; + + // These are temporary state variables to hold the argument to + // Read/WriteSegments so it can be accessed by On(read/write)segment + // further up the stack. + nsAHttpSegmentReader *mSegmentReader; + nsAHttpSegmentWriter *mSegmentWriter; + + PRUint32 mSendingChunkSize; /* the transmisison chunk size */ + PRUint32 mNextStreamID; /* 24 bits */ + PRUint32 mConcurrentHighWater; /* max parallelism on session */ + + stateType mDownstreamState; /* in frame, between frames, etc.. */ + + // Maintain 5 indexes - one by stream ID, one by transaction ptr, + // one list of streams ready to write, one list of streams that are queued + // due to max parallelism settings, and one list of streams + // that must be given priority to write for window updates. The objects + // are not ref counted - they get destryoed + // by the nsClassHashtable implementation when they are removed from + // there. + nsDataHashtable<nsUint32HashKey, SpdyStream *> mStreamIDHash; + nsClassHashtable<nsPtrHashKey<nsAHttpTransaction>, + SpdyStream> mStreamTransactionHash; + nsDeque mReadyForWrite; + nsDeque mQueuedStreams; + + // UrgentForWrite is meant to carry window updates. They were defined in + // the v2 spec but apparently never implemented so are now scheduled to + // be removed. But they will be reintroduced for v3, so we will leave + // this queue in place to ease that transition. + nsDeque mUrgentForWrite; + + // If we block while wrting out a frame then this points to the stream + // that was blocked. When writing again that stream must be the first + // one to write. It is null if there is not a partial frame. + SpdyStream *mPartialFrame; + + // Compression contexts for header transport using deflate. + // SPDY compresses only HTTP headers and does not reset zlib in between + // frames. + z_stream mDownstreamZlib; + z_stream mUpstreamZlib; + + // mFrameBuffer is used to store received control packets and the 8 bytes + // of header on data packets + PRUint32 mFrameBufferSize; + PRUint32 mFrameBufferUsed; + nsAutoArrayPtr<char> mFrameBuffer; + + // mFrameDataSize/Read are used for tracking the amount of data consumed + // in a data frame. the data itself is not buffered in spdy + // The frame size is mFrameDataSize + the constant 8 byte header + PRUint32 mFrameDataSize; + PRUint32 mFrameDataRead; + bool mFrameDataLast; // This frame was marked FIN + + // When a frame has been received that is addressed to a particular stream + // (e.g. a data frame after the stream-id has been decoded), this points + // to the stream. + SpdyStream *mFrameDataStream; + + // A state variable to cleanup a closed stream after the stack has unwound. + SpdyStream *mNeedsCleanup; + + // The CONTROL_TYPE value for a control frame + PRUint32 mFrameControlType; + + // This reason code in the last processed RESET frame + PRUint32 mDownstreamRstReason; + + // These are used for decompressing downstream spdy response headers + // This is done at the session level because sometimes the stream + // has already been canceled but the decompression still must happen + // to keep the zlib state correct for the next state of headers. + PRUint32 mDecompressBufferSize; + PRUint32 mDecompressBufferUsed; + nsAutoArrayPtr<char> mDecompressBuffer; + + // for the conversion of downstream http headers into spdy formatted headers + nsCString mFlatHTTPResponseHeaders; + PRUint32 mFlatHTTPResponseHeadersOut; + + // when set, the session will go away when it reaches 0 streams + bool mShouldGoAway; + + // the session has received a nsAHttpTransaction::Close() call + bool mClosed; + + // the session received a GoAway frame with a valid GoAwayID + bool mCleanShutdown; + + // If a GoAway message was received this is the ID of the last valid + // stream. 0 otherwise. (0 is never a valid stream id.) + PRUint32 mGoAwayID; + + // The limit on number of concurrent streams for this session. Normally it + // is basically unlimited, but the SETTINGS control message from the + // server might bring it down. + PRUint32 mMaxConcurrent; + + // The actual number of concurrent streams at this moment. Generally below + // mMaxConcurrent, but the max can be lowered in real time to a value + // below the current value + PRUint32 mConcurrent; + + // The number of server initiated SYN-STREAMS, tracked for telemetry + PRUint32 mServerPushedResources; + + // This is a output queue of bytes ready to be written to the SSL stream. + // When that streams returns WOULD_BLOCK on direct write the bytes get + // coalesced together here. This results in larger writes to the SSL layer. + // The buffer is not dynamically grown to accomodate stream writes, but + // does expand to accept infallible session wide frames like GoAway and RST. + PRUint32 mOutputQueueSize; + PRUint32 mOutputQueueUsed; + PRUint32 mOutputQueueSent; + nsAutoArrayPtr<char> mOutputQueueBuffer; +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_SpdySession_h
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/http/SpdyStream.cpp @@ -0,0 +1,849 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus <mcmanus@ducksong.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsHttp.h" +#include "SpdySession.h" +#include "SpdyStream.h" +#include "nsAlgorithm.h" +#include "prnetdb.h" +#include "nsHttpRequestHead.h" +#include "mozilla/Telemetry.h" +#include "nsISocketTransport.h" +#include "nsISupportsPriority.h" + +#ifdef DEBUG +// defined by the socket transport service while active +extern PRThread *gSocketThread; +#endif + +namespace mozilla { +namespace net { + +SpdyStream::SpdyStream(nsAHttpTransaction *httpTransaction, + SpdySession *spdySession, + nsISocketTransport *socketTransport, + PRUint32 chunkSize, + z_stream *compressionContext, + PRInt32 priority) + : mUpstreamState(GENERATING_SYN_STREAM), + mTransaction(httpTransaction), + mSession(spdySession), + mSocketTransport(socketTransport), + mSegmentReader(nsnull), + mSegmentWriter(nsnull), + mStreamID(0), + mChunkSize(chunkSize), + mSynFrameComplete(0), + mBlockedOnWrite(0), + mRequestBlockedOnRead(0), + mSentFinOnData(0), + mRecvdFin(0), + mFullyOpen(0), + mTxInlineFrameAllocation(SpdySession::kDefaultBufferSize), + mTxInlineFrameSize(0), + mTxInlineFrameSent(0), + mTxStreamFrameSize(0), + mTxStreamFrameSent(0), + mZlib(compressionContext), + mRequestBodyLen(0), + mPriority(priority) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + LOG3(("SpdyStream::SpdyStream %p", this)); + + mTxInlineFrame = new char[mTxInlineFrameAllocation]; +} + +SpdyStream::~SpdyStream() +{ +} + +// ReadSegments() is used to write data down the socket. Generally, HTTP +// request data is pulled from the approriate transaction and +// converted to SPDY data. Sometimes control data like a window-update is +// generated instead. + +nsresult +SpdyStream::ReadSegments(nsAHttpSegmentReader *reader, + PRUint32 count, + PRUint32 *countRead) +{ + LOG3(("SpdyStream %p ReadSegments reader=%p count=%d state=%x", + this, reader, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv = NS_ERROR_UNEXPECTED; + mBlockedOnWrite = 0; + mRequestBlockedOnRead = 0; + + switch (mUpstreamState) { + case GENERATING_SYN_STREAM: + case GENERATING_REQUEST_BODY: + case SENDING_REQUEST_BODY: + // Call into the HTTP Transaction to generate the HTTP request + // stream. That stream will show up in OnReadSegment(). + mSegmentReader = reader; + rv = mTransaction->ReadSegments(this, count, countRead); + mSegmentReader = nsnull; + + if (NS_SUCCEEDED(rv) && + mUpstreamState == GENERATING_SYN_STREAM && + !mSynFrameComplete) + mBlockedOnWrite = 1; + + // Mark that we are blocked on read if we the http transaction + // is going to get us going again. + if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mBlockedOnWrite) + mRequestBlockedOnRead = 1; + + if (!mBlockedOnWrite && NS_SUCCEEDED(rv) && (!*countRead)) { + LOG3(("ReadSegments %p Send Request data complete from %x", + this, mUpstreamState)); + if (mSentFinOnData) { + ChangeState(UPSTREAM_COMPLETE); + } + else { + GenerateDataFrameHeader(0, true); + ChangeState(SENDING_FIN_STREAM); + mBlockedOnWrite = 1; + rv = NS_BASE_STREAM_WOULD_BLOCK; + } + } + + break; + + case SENDING_SYN_STREAM: + // We were trying to send the SYN-STREAM but only got part of it out + // before being blocked. Try and send more. + mSegmentReader = reader; + rv = TransmitFrame(nsnull, nsnull); + mSegmentReader = nsnull; + *countRead = 0; + if (NS_SUCCEEDED(rv)) + rv = NS_BASE_STREAM_WOULD_BLOCK; + + if (!mTxInlineFrameSize) { + if (mSentFinOnData) { + ChangeState(UPSTREAM_COMPLETE); + rv = NS_OK; + } + else { + ChangeState(GENERATING_REQUEST_BODY); + mBlockedOnWrite = 1; + } + } + break; + + case SENDING_FIN_STREAM: + // We were trying to send the SYN-STREAM but only got part of it out + // before being blocked. Try and send more. + if (!mSentFinOnData) { + mSegmentReader = reader; + rv = TransmitFrame(nsnull, nsnull); + mSegmentReader = nsnull; + if (!mTxInlineFrameSize) + ChangeState(UPSTREAM_COMPLETE); + } + else { + rv = NS_OK; + mTxInlineFrameSize = 0; // cancel fin data packet + ChangeState(UPSTREAM_COMPLETE); + } + + *countRead = 0; + + // don't change OK to WOULD BLOCK. we are really done sending if OK + break; + + default: + NS_ABORT_IF_FALSE(false, "SpdyStream::ReadSegments unknown state"); + break; + } + + return rv; +} + +// WriteSegments() is used to read data off the socket. Generally this is +// just the SPDY frame header and from there the appropriate SPDYStream +// is identified from the Stream-ID. The http transaction associated with +// that read then pulls in the data directly. + +nsresult +SpdyStream::WriteSegments(nsAHttpSegmentWriter *writer, + PRUint32 count, + PRUint32 *countWritten) +{ + LOG3(("SpdyStream::WriteSegments %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(!mSegmentWriter, "segment writer in progress"); + + mSegmentWriter = writer; + nsresult rv = mTransaction->WriteSegments(writer, count, countWritten); + mSegmentWriter = nsnull; + return rv; +} + +PLDHashOperator +SpdyStream::hdrHashEnumerate(const nsACString &key, + nsAutoPtr<nsCString> &value, + void *closure) +{ + SpdyStream *self = static_cast<SpdyStream *>(closure); + + self->CompressToFrame(key); + self->CompressToFrame(value.get()); + return PL_DHASH_NEXT; +} + +nsresult +SpdyStream::ParseHttpRequestHeaders(const char *buf, + PRUint32 avail, + PRUint32 *countUsed) +{ + // Returns NS_OK even if the headers are incomplete + // set mSynFrameComplete flag if they are complete + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mUpstreamState == GENERATING_SYN_STREAM, "wrong state"); + + LOG3(("SpdyStream::ParseHttpRequestHeaders %p avail=%d state=%x", + this, avail, mUpstreamState)); + + mFlatHttpRequestHeaders.Append(buf, avail); + + // We can use the simple double crlf because firefox is the + // only client we are parsing + PRInt32 endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); + + if (endHeader == -1) { + // We don't have all the headers yet + LOG3(("SpdyStream::ParseHttpRequestHeaders %p " + "Need more header bytes. Len = %d", + this, mFlatHttpRequestHeaders.Length())); + *countUsed = avail; + return NS_OK; + } + + // We have recvd all the headers, trim the local + // buffer of the final empty line, and set countUsed to reflect + // the whole header has been consumed. + PRUint32 oldLen = mFlatHttpRequestHeaders.Length(); + mFlatHttpRequestHeaders.SetLength(endHeader + 2); + *countUsed = avail - (oldLen - endHeader) + 4; + mSynFrameComplete = 1; + + // It is now OK to assign a streamID that we are assured will + // be monotonically increasing amongst syn-streams on this + // session + mStreamID = mSession->RegisterStreamID(this); + NS_ABORT_IF_FALSE(mStreamID & 1, + "Spdy Stream Channel ID must be odd"); + + if (mStreamID >= 0x80000000) { + // streamID must fit in 31 bits. This is theoretically possible + // because stream ID assignment is asynchronous to stream creation + // because of the protocol requirement that the ID in syn-stream + // be monotonically increasing. In reality this is really not possible + // because new streams stop being added to a session with 0x10000000 / 2 + // IDs still available and no race condition is going to bridge that gap, + // so we can be comfortable on just erroring out for correctness in that + // case. + LOG3(("Stream assigned out of range ID: 0x%X", mStreamID)); + return NS_ERROR_UNEXPECTED; + } + + // Now we need to convert the flat http headers into a set + // of SPDY headers.. writing to mTxInlineFrame{sz} + + mTxInlineFrame[0] = SpdySession::kFlag_Control; + mTxInlineFrame[1] = 2; /* version */ + mTxInlineFrame[2] = 0; + mTxInlineFrame[3] = SpdySession::CONTROL_TYPE_SYN_STREAM; + // 4 to 7 are length and flags, we'll fill that in later + + PRUint32 networkOrderID = PR_htonl(mStreamID); + memcpy(mTxInlineFrame + 8, &networkOrderID, 4); + + // this is the associated-to field, which is not used sending + // from the client in the http binding + memset (mTxInlineFrame + 12, 0, 4); + + // Priority flags are the C0 mask of byte 16. + // From low to high: 00 40 80 C0 + // higher raw priority values are actually less important + // + // The other 6 bits of 16 are unused. Spdy/3 will expand + // priority to 4 bits. + // + // When Spdy/3 implements WINDOW_UPDATE the lowest priority + // streams over a threshold (32?) should be given tiny + // receive windows, separate from their spdy priority + // + if (mPriority >= nsISupportsPriority::PRIORITY_LOW) + mTxInlineFrame[16] = SpdySession::kPri00; + else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL) + mTxInlineFrame[16] = SpdySession::kPri01; + else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH) + mTxInlineFrame[16] = SpdySession::kPri02; + else + mTxInlineFrame[16] = SpdySession::kPri03; + + mTxInlineFrame[17] = 0; /* unused */ + +// nsCString methodHeader; +// mTransaction->RequestHead()->Method()->ToUTF8String(methodHeader); + const char *methodHeader = mTransaction->RequestHead()->Method().get(); + + nsCString hostHeader; + mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); + + nsCString versionHeader; + if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1) + versionHeader = NS_LITERAL_CSTRING("HTTP/1.1"); + else + versionHeader = NS_LITERAL_CSTRING("HTTP/1.0"); + + nsClassHashtable<nsCStringHashKey, nsCString> hdrHash; + + // use mRequestHead() to get a sense of how big to make the hash, + // even though we are parsing the actual text stream because + // it is legit to append headers. + hdrHash.Init(1 + (mTransaction->RequestHead()->Headers().Count() * 2)); + + const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); + + // need to hash all the headers together to remove duplicates, special + // headers, etc.. + + PRInt32 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n"); + while (true) { + PRInt32 startIndex = crlfIndex + 2; + + crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex); + if (crlfIndex == -1) + break; + + PRInt32 colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex, + crlfIndex - startIndex); + if (colonIndex == -1) + break; + + nsDependentCSubstring name = Substring(beginBuffer + startIndex, + beginBuffer + colonIndex); + // all header names are lower case in spdy + ToLowerCase(name); + + if (name.Equals("method") || + name.Equals("version") || + name.Equals("scheme") || + name.Equals("keep-alive") || + name.Equals("accept-encoding") || + name.Equals("te") || + name.Equals("connection") || + name.Equals("proxy-connection") || + name.Equals("url")) + continue; + + nsCString *val = hdrHash.Get(name); + if (!val) { + val = new nsCString(); + hdrHash.Put(name, val); + } + + PRInt32 valueIndex = colonIndex + 1; + while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') + ++valueIndex; + + nsDependentCSubstring v = Substring(beginBuffer + valueIndex, + beginBuffer + crlfIndex); + if (!val->IsEmpty()) + val->Append(static_cast<char>(0)); + val->Append(v); + + if (name.Equals("content-length")) { + PRInt64 len; + if (nsHttp::ParseInt64(val->get(), nsnull, &len)) + mRequestBodyLen = len; + } + } + + mTxInlineFrameSize = 18; + + LOG3(("http request headers to encode are: \n%s", + mFlatHttpRequestHeaders.get())); + + // The header block length + PRUint16 count = hdrHash.Count() + 4; /* method, scheme, url, version */ + CompressToFrame(count); + + // method, scheme, url, and version headers for request line + + CompressToFrame(NS_LITERAL_CSTRING("method")); + CompressToFrame(methodHeader, strlen(methodHeader)); + CompressToFrame(NS_LITERAL_CSTRING("scheme")); + CompressToFrame(NS_LITERAL_CSTRING("https")); + CompressToFrame(NS_LITERAL_CSTRING("url")); + CompressToFrame(mTransaction->RequestHead()->RequestURI()); + CompressToFrame(NS_LITERAL_CSTRING("version")); + CompressToFrame(versionHeader); + + hdrHash.Enumerate(hdrHashEnumerate, this); + CompressFlushFrame(); + + // 4 to 7 are length and flags, which we can now fill in + (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[1] = + PR_htonl(mTxInlineFrameSize - 8); + + NS_ABORT_IF_FALSE(!mTxInlineFrame[4], + "Size greater than 24 bits"); + + // For methods other than POST and PUT, we will set the fin bit + // right on the syn stream packet. + + if (mTransaction->RequestHead()->Method() != nsHttp::Post && + mTransaction->RequestHead()->Method() != nsHttp::Put) { + mSentFinOnData = 1; + mTxInlineFrame[4] = SpdySession::kFlag_Data_FIN; + } + + Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameSize - 18); + + // The size of the input headers is approximate + PRUint32 ratio = + (mTxInlineFrameSize - 18) * 100 / + (11 + mTransaction->RequestHead()->RequestURI().Length() + + mFlatHttpRequestHeaders.Length()); + + Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); + return NS_OK; +} + +nsresult +SpdyStream::TransmitFrame(const char *buf, + PRUint32 *countUsed) +{ + NS_ABORT_IF_FALSE(mTxInlineFrameSize, "empty stream frame in transmit"); + NS_ABORT_IF_FALSE(mSegmentReader, "TransmitFrame with null mSegmentReader"); + + PRUint32 transmittedCount; + nsresult rv; + + LOG3(("SpdyStream::TransmitFrame %p inline=%d of %d stream=%d of %d", + this, mTxInlineFrameSent, mTxInlineFrameSize, + mTxStreamFrameSent, mTxStreamFrameSize)); + if (countUsed) + *countUsed = 0; + mBlockedOnWrite = 0; + + // In the (relatively common) event that we have a small amount of data + // split between the inlineframe and the streamframe, then move the stream + // data into the inlineframe via copy in order to coalesce into one write. + // Given the interaction with ssl this is worth the small copy cost. + if (mTxStreamFrameSize && mTxInlineFrameSize && + !mTxInlineFrameSent && !mTxStreamFrameSent && + mTxStreamFrameSize < SpdySession::kDefaultBufferSize && + mTxInlineFrameSize + mTxStreamFrameSize < mTxInlineFrameAllocation) { + LOG3(("Coalesce Transmit")); + memcpy (mTxInlineFrame + mTxInlineFrameSize, + buf, mTxStreamFrameSize); + mTxInlineFrameSize += mTxStreamFrameSize; + mTxStreamFrameSent = 0; + mTxStreamFrameSize = 0; + } + + // This function calls mSegmentReader->OnReadSegment to report the actual SPDY + // bytes through to the SpdySession and then the HttpConnection which calls + // the socket write function. + + while (mTxInlineFrameSent < mTxInlineFrameSize) { + rv = mSegmentReader->OnReadSegment(mTxInlineFrame + mTxInlineFrameSent, + mTxInlineFrameSize - mTxInlineFrameSent, + &transmittedCount); + LOG3(("SpdyStream::TransmitFrame for inline session=%p " + "stream=%p result %x len=%d", + mSession, this, rv, transmittedCount)); + SpdySession::LogIO(mSession, this, "Writing from Inline Buffer", + mTxInlineFrame + mTxInlineFrameSent, + transmittedCount); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + mBlockedOnWrite = 1; + + if (NS_FAILED(rv)) // this will include WOULD_BLOCK + return rv; + + mTxInlineFrameSent += transmittedCount; + } + + PRUint32 offset = 0; + NS_ABORT_IF_FALSE(mTxStreamFrameSize >= mTxStreamFrameSent, + "negative unsent"); + PRUint32 avail = mTxStreamFrameSize - mTxStreamFrameSent; + + while (avail) { + NS_ABORT_IF_FALSE(countUsed, "null countused pointer in a stream context"); + rv = mSegmentReader->OnReadSegment(buf + offset, avail, &transmittedCount); + + LOG3(("SpdyStream::TransmitFrame for regular session=%p " + "stream=%p result %x len=%d", + mSession, this, rv, transmittedCount)); + SpdySession::LogIO(mSession, this, "Writing from Transaction Buffer", + buf + offset, transmittedCount); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + mBlockedOnWrite = 1; + + if (NS_FAILED(rv)) // this will include WOULD_BLOCK + return rv; + + if (mUpstreamState == SENDING_REQUEST_BODY) { + mTransaction->OnTransportStatus(mSocketTransport, + nsISocketTransport::STATUS_SENDING_TO, + transmittedCount); + } + + *countUsed += transmittedCount; + avail -= transmittedCount; + offset += transmittedCount; + mTxStreamFrameSent += transmittedCount; + } + + if (!avail) { + mTxInlineFrameSent = 0; + mTxInlineFrameSize = 0; + mTxStreamFrameSent = 0; + mTxStreamFrameSize = 0; + } + + return NS_OK; +} + +void +SpdyStream::ChangeState(enum stateType newState) +{ + LOG3(("SpdyStream::ChangeState() %p from %X to %X", + this, mUpstreamState, newState)); + mUpstreamState = newState; + return; +} + +void +SpdyStream::GenerateDataFrameHeader(PRUint32 dataLength, bool lastFrame) +{ + LOG3(("SpdyStream::GenerateDataFrameHeader %p len=%d last=%d", + this, dataLength, lastFrame)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(!mTxInlineFrameSize, "inline frame not empty"); + NS_ABORT_IF_FALSE(!mTxInlineFrameSent, "inline partial send not 0"); + NS_ABORT_IF_FALSE(!mTxStreamFrameSize, "stream frame not empty"); + NS_ABORT_IF_FALSE(!mTxStreamFrameSent, "stream partial send not 0"); + NS_ABORT_IF_FALSE(!(dataLength & 0xff000000), "datalength > 24 bits"); + + (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID); + (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[1] = + PR_htonl(dataLength); + + NS_ABORT_IF_FALSE(!(mTxInlineFrame[0] & 0x80), + "control bit set unexpectedly"); + NS_ABORT_IF_FALSE(!mTxInlineFrame[4], "flag bits set unexpectedly"); + + mTxInlineFrameSize = 8; + mTxStreamFrameSize = dataLength; + + if (lastFrame) { + mTxInlineFrame[4] |= SpdySession::kFlag_Data_FIN; + if (dataLength) + mSentFinOnData = 1; + } +} + +void +SpdyStream::CompressToFrame(const nsACString &str) +{ + CompressToFrame(str.BeginReading(), str.Length()); +} + +void +SpdyStream::CompressToFrame(const nsACString *str) +{ + CompressToFrame(str->BeginReading(), str->Length()); +} + +// Dictionary taken from +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 +// Name/Value Header Block Format +// spec indicates that the compression dictionary is not null terminated +// but in reality it is. see: +// https://groups.google.com/forum/#!topic/spdy-dev/2pWxxOZEIcs + +const char *SpdyStream::kDictionary = + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" + "-agent10010120020120220320420520630030130230330430530630740040140240340440" + "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" + "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" + "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" + "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" + "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" + "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" + "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" + ".1statusversionurl"; + +// use for zlib data types +void * +SpdyStream::zlib_allocator(void *opaque, uInt items, uInt size) +{ + return moz_xmalloc(items * size); +} + +// use for zlib data types +void +SpdyStream::zlib_destructor(void *opaque, void *addr) +{ + moz_free(addr); +} + +void +SpdyStream::ExecuteCompress(PRUint32 flushMode) +{ + // Expect mZlib->avail_in and mZlib->next_in to be set. + // Append the compressed version of next_in to mTxInlineFrame + + do + { + PRUint32 avail = mTxInlineFrameAllocation - mTxInlineFrameSize; + if (avail < 1) { + SpdySession::EnsureBuffer(mTxInlineFrame, + mTxInlineFrameAllocation + 2000, + mTxInlineFrameSize, + mTxInlineFrameAllocation); + avail = mTxInlineFrameAllocation - mTxInlineFrameSize; + } + + mZlib->next_out = reinterpret_cast<unsigned char *> (mTxInlineFrame.get()) + + mTxInlineFrameSize; + mZlib->avail_out = avail; + deflate(mZlib, flushMode); + mTxInlineFrameSize += avail - mZlib->avail_out; + } while (mZlib->avail_in > 0 || !mZlib->avail_out); +} + +void +SpdyStream::CompressToFrame(PRUint16 data) +{ + // convert the data to network byte order and write that + // to the compressed stream + + data = PR_htons(data); + + mZlib->next_in = reinterpret_cast<unsigned char *> (&data); + mZlib->avail_in = 2; + ExecuteCompress(Z_NO_FLUSH); +} + + +void +SpdyStream::CompressToFrame(const char *data, PRUint32 len) +{ + // Format calls for a network ordered 16 bit length + // followed by the utf8 string + + // for now, silently truncate headers greater than 64KB. Spdy/3 will + // fix this by making the len a 32 bit quantity + if (len > 0xffff) + len = 0xffff; + + PRUint16 networkLen = len; + networkLen = PR_htons(len); + + // write out the length + mZlib->next_in = reinterpret_cast<unsigned char *> (&networkLen); + mZlib->avail_in = 2; + ExecuteCompress(Z_NO_FLUSH); + + // write out the data + mZlib->next_in = (unsigned char *)data; + mZlib->avail_in = len; + ExecuteCompress(Z_NO_FLUSH); +} + +void +SpdyStream::CompressFlushFrame() +{ + mZlib->next_in = (unsigned char *) ""; + mZlib->avail_in = 0; + ExecuteCompress(Z_SYNC_FLUSH); +} + +void +SpdyStream::Close(nsresult reason) +{ + mTransaction->Close(reason); +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentReader +//----------------------------------------------------------------------------- + +nsresult +SpdyStream::OnReadSegment(const char *buf, + PRUint32 count, + PRUint32 *countRead) +{ + LOG3(("SpdyStream::OnReadSegment %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentReader, "OnReadSegment with null mSegmentReader"); + + nsresult rv = NS_ERROR_UNEXPECTED; + PRUint32 dataLength; + + switch (mUpstreamState) { + case GENERATING_SYN_STREAM: + // The buffer is the HTTP request stream, including at least part of the + // HTTP request header. This state's job is to build a SYN_STREAM frame + // from the header information. count is the number of http bytes available + // (which may include more than the header), and in countRead we return + // the number of those bytes that we consume (i.e. the portion that are + // header bytes) + + rv = ParseHttpRequestHeaders(buf, count, countRead); + if (NS_FAILED(rv)) + return rv; + LOG3(("ParseHttpRequestHeaders %p used %d of %d.", + this, *countRead, count)); + if (mSynFrameComplete) { + NS_ABORT_IF_FALSE(mTxInlineFrameSize, + "OnReadSegment SynFrameComplete 0b"); + rv = TransmitFrame(nsnull, nsnull); + if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) + rv = NS_OK; + if (mTxInlineFrameSize) + ChangeState(SENDING_SYN_STREAM); + else + ChangeState(GENERATING_REQUEST_BODY); + break; + } + NS_ABORT_IF_FALSE(*countRead == count, + "Header parsing not complete but unused data"); + break; + + case GENERATING_REQUEST_BODY: + NS_ABORT_IF_FALSE(!mTxInlineFrameSent, + "OnReadSegment in generating_request_body with " + "frame in progress"); + if (count < mChunkSize && count < mRequestBodyLen) { + LOG3(("SpdyStream %p id %x has %d to write out of a bodylen %d" + " with a chunk size of %d. Waiting for more.", + this, mStreamID, count, mChunkSize, mRequestBodyLen)); + rv = NS_BASE_STREAM_WOULD_BLOCK; + break; + } + + dataLength = NS_MIN(count, mChunkSize); + if (dataLength > mRequestBodyLen) + return NS_ERROR_UNEXPECTED; + mRequestBodyLen -= dataLength; + GenerateDataFrameHeader(dataLength, !mRequestBodyLen); + ChangeState(SENDING_REQUEST_BODY); + // NO BREAK + + case SENDING_REQUEST_BODY: + NS_ABORT_IF_FALSE(mTxInlineFrameSize, "OnReadSegment Send Data Header 0b"); + rv = TransmitFrame(buf, countRead); + LOG3(("TransmitFrame() rv=%x returning %d data bytes. " + "Header is %d/%d Body is %d/%d.", + rv, *countRead, + mTxInlineFrameSent, mTxInlineFrameSize, + mTxStreamFrameSent, mTxStreamFrameSize)); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) + rv = NS_OK; + + // If that frame was all sent, look for another one + if (!mTxInlineFrameSize) + ChangeState(GENERATING_REQUEST_BODY); + break; + + case SENDING_SYN_STREAM: + rv = NS_BASE_STREAM_WOULD_BLOCK; + break; + + case SENDING_FIN_STREAM: + NS_ABORT_IF_FALSE(false, + "resuming partial fin stream out of OnReadSegment"); + break; + + default: + NS_ABORT_IF_FALSE(false, "SpdyStream::OnReadSegment non-write state"); + break; + } + + return rv; +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentWriter +//----------------------------------------------------------------------------- + +nsresult +SpdyStream::OnWriteSegment(char *buf, + PRUint32 count, + PRUint32 *countWritten) +{ + LOG3(("SpdyStream::OnWriteSegment %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter"); + + return mSegmentWriter->OnWriteSegment(buf, count, countWritten); +} + +} // namespace mozilla::net +} // namespace mozilla +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/http/SpdyStream.h @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus <mcmanus@ducksong.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_net_SpdyStream_h +#define mozilla_net_SpdyStream_h + +#include "nsAHttpTransaction.h" + +namespace mozilla { namespace net { + +class SpdyStream : public nsAHttpSegmentReader + , public nsAHttpSegmentWriter +{ +public: + NS_DECL_NSAHTTPSEGMENTREADER + NS_DECL_NSAHTTPSEGMENTWRITER + + SpdyStream(nsAHttpTransaction *, + SpdySession *, nsISocketTransport *, + PRUint32, z_stream *, PRInt32); + ~SpdyStream(); + + PRUint32 StreamID() { return mStreamID; } + + nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); + nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *); + + bool BlockedOnWrite() + { + return static_cast<bool>(mBlockedOnWrite); + } + + bool RequestBlockedOnRead() + { + return static_cast<bool>(mRequestBlockedOnRead); + } + + // returns false if called more than once + bool SetFullyOpen() + { + bool result = !mFullyOpen; + mFullyOpen = 1; + return result; + } + + nsAHttpTransaction *Transaction() + { + return mTransaction; + } + + void Close(nsresult reason); + + void SetRecvdFin(bool aStatus) { mRecvdFin = aStatus ? 1 : 0; } + bool RecvdFin() { return mRecvdFin; } + + // The zlib header compression dictionary defined by SPDY, + // and hooks to the mozilla allocator for zlib to use. + static const char *kDictionary; + static void *zlib_allocator(void *, uInt, uInt); + static void zlib_destructor(void *, void *); + +private: + + enum stateType { + GENERATING_SYN_STREAM, + SENDING_SYN_STREAM, + GENERATING_REQUEST_BODY, + SENDING_REQUEST_BODY, + SENDING_FIN_STREAM, + UPSTREAM_COMPLETE + }; + + static PLDHashOperator hdrHashEnumerate(const nsACString &, + nsAutoPtr<nsCString> &, + void *); + + void ChangeState(enum stateType ); + nsresult ParseHttpRequestHeaders(const char *, PRUint32, PRUint32 *); + nsresult TransmitFrame(const char *, PRUint32 *); + void GenerateDataFrameHeader(PRUint32, bool); + + void CompressToFrame(const nsACString &); + void CompressToFrame(const nsACString *); + void CompressToFrame(const char *, PRUint32); + void CompressToFrame(PRUint16); + void CompressFlushFrame(); + void ExecuteCompress(PRUint32); + + // Each stream goes from syn_stream to upstream_complete, perhaps + // looping on multiple instances of generating_request_body and + // sending_request_body for each SPDY chunk in the upload. + enum stateType mUpstreamState; + + // The underlying HTTP transaction + nsRefPtr<nsAHttpTransaction> mTransaction; + + // The session that this stream is a subset of + SpdySession *mSession; + + // The underlying socket transport object is needed to propogate some events + nsISocketTransport *mSocketTransport; + + // These are temporary state variables to hold the argument to + // Read/WriteSegments so it can be accessed by On(read/write)segment + // further up the stack. + nsAHttpSegmentReader *mSegmentReader; + nsAHttpSegmentWriter *mSegmentWriter; + + // The 24 bit SPDY stream ID + PRUint32 mStreamID; + + // The quanta upstream data frames are chopped into + PRUint32 mChunkSize; + + // Flag is set when all http request headers have been read + PRUint32 mSynFrameComplete : 1; + + // Flag is set when there is more request data to send and the + // stream needs to be rescheduled for writing. Sometimes this + // is done as a matter of fairness, not really due to blocking + PRUint32 mBlockedOnWrite : 1; + + // Flag is set when the HTTP processor has more data to send + // but has blocked in doing so. + PRUint32 mRequestBlockedOnRead : 1; + + // Flag is set when a FIN has been placed on a data or syn packet + // (i.e after the client has closed) + PRUint32 mSentFinOnData : 1; + + // Flag is set after the response frame bearing the fin bit has + // been processed. (i.e. after the server has closed). + PRUint32 mRecvdFin : 1; + + // Flag is set after syn reply received + PRUint32 mFullyOpen : 1; + + // The InlineFrame and associated data is used for composing control + // frames and data frame headers. + nsAutoArrayPtr<char> mTxInlineFrame; + PRUint32 mTxInlineFrameAllocation; + PRUint32 mTxInlineFrameSize; + PRUint32 mTxInlineFrameSent; + + // mTxStreamFrameSize and mTxStreamFrameSent track the progress of + // transmitting a request body data frame. The data frame itself + // is never copied into the spdy layer. + PRUint32 mTxStreamFrameSize; + PRUint32 mTxStreamFrameSent; + + // Compression context and buffer for request header compression. + // This is a copy of SpdySession::mUpstreamZlib because it needs + // to remain the same in all streams of a session. + z_stream *mZlib; + nsCString mFlatHttpRequestHeaders; + + // Track the content-length of a request body so that we can + // place the fin flag on the last data packet instead of waiting + // for a stream closed indication. Relying on stream close results + // in an extra 0-length runt packet and seems to have some interop + // problems with the google servers. + PRInt64 mRequestBodyLen; + + // based on nsISupportsPriority definitions + PRInt32 mPriority; + +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_SpdyStream_h
--- a/netwerk/protocol/http/nsAHttpConnection.h +++ b/netwerk/protocol/http/nsAHttpConnection.h @@ -72,18 +72,18 @@ public: nsHttpResponseHead *, bool *reset) = 0; // // called by a transaction to resume either sending or receiving data // after a transaction returned NS_BASE_STREAM_WOULD_BLOCK from its // ReadSegments/WriteSegments methods. // - virtual nsresult ResumeSend() = 0; - virtual nsresult ResumeRecv() = 0; + virtual nsresult ResumeSend(nsAHttpTransaction *caller) = 0; + virtual nsresult ResumeRecv(nsAHttpTransaction *caller) = 0; // // called by the connection manager to close a transaction being processed // by this connection. // // @param transaction // the transaction being closed. // @param reason @@ -127,18 +127,18 @@ public: // Get the nsISocketTransport used by the connection without changing // references or ownership. virtual nsISocketTransport *Transport() = 0; }; #define NS_DECL_NSAHTTPCONNECTION \ nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset); \ - nsresult ResumeSend(); \ - nsresult ResumeRecv(); \ + nsresult ResumeSend(nsAHttpTransaction *); \ + nsresult ResumeRecv(nsAHttpTransaction *); \ void CloseTransaction(nsAHttpTransaction *, nsresult); \ void GetConnectionInfo(nsHttpConnectionInfo **); \ nsresult TakeTransport(nsISocketTransport **, \ nsIAsyncInputStream **, \ nsIAsyncOutputStream **); \ void GetSecurityInfo(nsISupports **); \ bool IsPersistent(); \ bool IsReused(); \
--- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -57,16 +57,17 @@ class nsHttpRequestHead; // write function returns NS_BASE_STREAM_WOULD_BLOCK in this case). //---------------------------------------------------------------------------- class nsAHttpTransaction : public nsISupports { public: // called by the connection when it takes ownership of the transaction. virtual void SetConnection(nsAHttpConnection *) = 0; + virtual nsAHttpConnection *Connection() = 0; // called by the connection to get security callbacks to set on the // socket transport. virtual void GetSecurityCallbacks(nsIInterfaceRequestor **, nsIEventTarget **) = 0; // called to report socket status (see nsITransportEventSink) virtual void OnTransportStatus(nsITransport* transport, @@ -94,16 +95,17 @@ public: virtual void SetSSLConnectFailed() = 0; // called to retrieve the request headers of the transaction virtual nsHttpRequestHead *RequestHead() = 0; }; #define NS_DECL_NSAHTTPTRANSACTION \ void SetConnection(nsAHttpConnection *); \ + nsAHttpConnection *Connection(); \ void GetSecurityCallbacks(nsIInterfaceRequestor **, \ nsIEventTarget **); \ void OnTransportStatus(nsITransport* transport, \ nsresult status, PRUint64 progress); \ bool IsDone(); \ nsresult Status(); \ PRUint32 Available(); \ nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); \
--- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -126,16 +126,21 @@ typedef PRUint8 nsHttpVersion; // a transaction with this caps flag keeps timing information #define NS_HTTP_TIMING_ENABLED (1<<5) // a transaction with this caps flag will not only not use an existing // persistent connection but it will close outstanding ones to the same // host. Used by a forced reload to reset the connection states. #define NS_HTTP_CLEAR_KEEPALIVES (1<<6) +// Disallow the use of the SPDY protocol. This is meant for the contexts +// such as HTTP upgrade which are nonsensical for SPDY, it is not the +// SPDY configuration variable. +#define NS_HTTP_DISALLOW_SPDY (1<<7) + //----------------------------------------------------------------------------- // some default values //----------------------------------------------------------------------------- // hard upper limit on the number of requests that can be pipelined #define NS_HTTP_MAX_PIPELINED_REQUESTS 8 #define NS_HTTP_DEFAULT_PORT 80
--- a/netwerk/protocol/http/nsHttpAtomList.h +++ b/netwerk/protocol/http/nsHttpAtomList.h @@ -51,16 +51,17 @@ ******/ HTTP_ATOM(Accept, "Accept") HTTP_ATOM(Accept_Encoding, "Accept-Encoding") HTTP_ATOM(Accept_Language, "Accept-Language") HTTP_ATOM(Accept_Ranges, "Accept-Ranges") HTTP_ATOM(Age, "Age") HTTP_ATOM(Allow, "Allow") +HTTP_ATOM(Alternate_Protocol, "Alternate-Protocol") HTTP_ATOM(Authentication, "Authentication") HTTP_ATOM(Authorization, "Authorization") HTTP_ATOM(Cache_Control, "Cache-Control") HTTP_ATOM(Connection, "Connection") HTTP_ATOM(Content_Base, "Content-Base") HTTP_ATOM(Content_Disposition, "Content-Disposition") HTTP_ATOM(Content_Encoding, "Content-Encoding") HTTP_ATOM(Content_Language, "Content-Language")
--- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -204,16 +204,28 @@ nsHttpChannel::Connect(bool firstTime) // worrisome. NS_ASSERTION(NS_SUCCEEDED(rv), "Something is wrong with STS: IsStsURI failed."); if (NS_SUCCEEDED(rv) && isStsHost) { LOG(("nsHttpChannel::Connect() STS permissions found\n")); return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); } + + // Check for a previous SPDY Alternate-Protocol directive + if (gHttpHandler->IsSpdyEnabled() && mAllowSpdy) { + nsCAutoString hostPort; + + if (NS_SUCCEEDED(mURI->GetHostPort(hostPort)) && + gHttpHandler->ConnMgr()->GetSpdyAlternateProtocol(hostPort)) { + LOG(("nsHttpChannel::Connect() Alternate-Protocol found\n")); + return AsyncCall( + &nsHttpChannel::HandleAsyncRedirectChannelToHttps); + } + } } // ensure that we are using a valid hostname if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host()))) return NS_ERROR_UNKNOWN_HOST; // true when called from AsyncOpen if (firstTime) { @@ -502,16 +514,19 @@ nsHttpChannel::SetupTransaction() mRequestHead.Method() == nsHttp::Head || mRequestHead.Method() == nsHttp::Propfind || mRequestHead.Method() == nsHttp::Proppatch)) { LOG((" pipelining disallowed\n")); mCaps &= ~NS_HTTP_ALLOW_PIPELINING; } } + if (!mAllowSpdy) + mCaps |= NS_HTTP_DISALLOW_SPDY; + // use the URI path if not proxying (transparent proxying such as SSL proxy // does not count here). also, figure out what version we should be speaking. nsCAutoString buf, path; nsCString* requestURI; if (mConnectionInfo->UsingSSL() || mConnectionInfo->ShouldForceConnectMethod() || !mConnectionInfo->UsingHttpProxy()) { rv = mURI->GetPath(path); @@ -629,16 +644,17 @@ nsHttpChannel::SetupTransaction() if (mUpgradeProtocolCallback) { mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false); mRequestHead.SetHeader(nsHttp::Connection, nsDependentCString(nsHttp::Upgrade.get()), true); mCaps |= NS_HTTP_STICKY_CONNECTION; mCaps &= ~NS_HTTP_ALLOW_PIPELINING; mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE; + mCaps |= NS_HTTP_DISALLOW_SPDY; } nsCOMPtr<nsIAsyncInputStream> responseStream; rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mUploadStreamHasHeaders, NS_GetCurrentThread(), callbacks, this, getter_AddRefs(responseStream)); if (NS_FAILED(rv)) { @@ -4085,16 +4101,26 @@ nsHttpChannel::OnStartRequest(nsIRequest "If we have both pumps, the cache content must be partial"); if (!mSecurityInfo && !mCachePump && mTransaction) { // grab the security info from the connection object; the transaction // is guaranteed to own a reference to the connection. mSecurityInfo = mTransaction->SecurityInfo(); } + if (gHttpHandler->IsSpdyEnabled() && !mCachePump && NS_FAILED(mStatus) && + (mLoadFlags & LOAD_REPLACE) && mOriginalURI && mAllowSpdy) { + // For sanity's sake we may want to cancel an alternate protocol + // redirection involving the original host name + + nsCAutoString hostPort; + if (NS_SUCCEEDED(mOriginalURI->GetHostPort(hostPort))) + gHttpHandler->ConnMgr()->RemoveSpdyAlternateProtocol(hostPort); + } + // don't enter this block if we're reading from the cache... if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) { // mTransactionPump doesn't hit OnInputStreamReady and call this until // all of the response headers have been acquired, so we can take ownership // of them from the transaction. mResponseHead = mTransaction->TakeResponseHead(); // the response head may be null if the transaction was cancelled. in // which case we just need to call OnStartRequest/OnStopRequest.
--- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -48,16 +48,19 @@ #include "nsIServiceManager.h" #include "nsISSLSocketControl.h" #include "nsStringStream.h" #include "netCore.h" #include "nsNetCID.h" #include "nsProxyRelease.h" #include "prmem.h" #include "nsPreloadedStream.h" +#include "SpdySession.h" +#include "mozilla/Telemetry.h" +#include "nsISupportsPriority.h" #ifdef DEBUG // defined by the socket transport service while active extern PRThread *gSocketThread; #endif static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); @@ -77,16 +80,21 @@ nsHttpConnection::nsHttpConnection() , mMaxBytesRead(0) , mKeepAlive(true) // assume to keep-alive by default , mKeepAliveMask(true) , mSupportsPipelining(false) // assume low-grade server , mIsReused(false) , mCompletedProxyConnect(false) , mLastTransactionExpectedNoContent(false) , mIdleMonitoring(false) + , mNPNComplete(false) + , mSetupNPNCalled(false) + , mUsingSpdy(false) + , mPriority(nsISupportsPriority::PRIORITY_NORMAL) + , mReportedSpdy(false) { LOG(("Creating nsHttpConnection @%x\n", this)); // grab a reference to the handler to ensure that it doesn't go away. nsHttpHandler *handler = gHttpHandler; NS_ADDREF(handler); } @@ -136,41 +144,117 @@ nsHttpConnection::Init(nsHttpConnectionI mCallbacks = callbacks; mCallbackTarget = callbackTarget; rv = mSocketTransport->SetSecurityCallbacks(this); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } +bool +nsHttpConnection::EnsureNPNComplete() +{ + // NPN is only used by SPDY right now. + // + // If for some reason the components to check on NPN aren't available, + // this function will just return true to continue on and disable SPDY + + NS_ABORT_IF_FALSE(mSocketTransport, "EnsureNPNComplete " + "socket transport precondition"); + + if (mNPNComplete) + return true; + + nsresult rv; + + nsCOMPtr<nsISupports> securityInfo; + nsCOMPtr<nsISSLSocketControl> ssl; + nsCAutoString negotiatedNPN; + + rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_FAILED(rv)) + goto npnComplete; + + ssl = do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + goto npnComplete; + + rv = ssl->GetNegotiatedNPN(negotiatedNPN); + if (rv == NS_ERROR_NOT_CONNECTED) { + + // By writing 0 bytes to the socket the SSL handshake machine is + // pushed forward. + PRUint32 count = 0; + rv = mSocketOut->Write("", 0, &count); + + if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) + goto npnComplete; + return false; + } + + if (NS_FAILED(rv)) + goto npnComplete; + + LOG(("nsHttpConnection::EnsureNPNComplete %p negotiated to '%s'", + this, negotiatedNPN.get())); + + if (negotiatedNPN.Equals(NS_LITERAL_CSTRING("spdy/2"))) { + + mUsingSpdy = true; + mIsReused = true; /* all spdy streams are reused */ + + // Wrap the old http transaction into the new spdy session + // as the first stream + mSpdySession = new SpdySession(mTransaction, + mSocketTransport, + mPriority); + mTransaction = mSpdySession; + mIdleTimeout = gHttpHandler->SpdyTimeout(); + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_CONNECT, + mUsingSpdy); + +npnComplete: + LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true")); + mNPNComplete = true; + return true; +} + // called on the socket thread nsresult -nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps) +nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps, PRInt32 pri) { nsresult rv; NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnection::Activate [this=%x trans=%x caps=%x]\n", this, trans, caps)); + mPriority = pri; + if (mTransaction && mUsingSpdy) + return AddTransaction(trans, pri); + NS_ENSURE_ARG_POINTER(trans); NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS); // Update security callbacks nsCOMPtr<nsIInterfaceRequestor> callbacks; nsCOMPtr<nsIEventTarget> callbackTarget; trans->GetSecurityCallbacks(getter_AddRefs(callbacks), getter_AddRefs(callbackTarget)); if (callbacks != mCallbacks) { mCallbacks.swap(callbacks); if (callbacks) NS_ProxyRelease(mCallbackTarget, callbacks); mCallbackTarget = callbackTarget; } + SetupNPN(caps); // only for spdy + // take ownership of the transaction mTransaction = trans; NS_ABORT_IF_FALSE(!mIdleMonitoring, "Activating a connection with an Idle Monitor"); mIdleMonitoring = false; // set mKeepAlive according to what will be requested @@ -194,16 +278,105 @@ failed_activation: if (NS_FAILED(rv)) { mTransaction = nsnull; } return rv; } void +nsHttpConnection::SetupNPN(PRUint8 caps) +{ + if (mSetupNPNCalled) /* do only once */ + return; + mSetupNPNCalled = true; + + // Setup NPN Negotiation if necessary (only for SPDY) + if (!mNPNComplete) { + + mNPNComplete = true; + + if (mConnInfo->UsingSSL() && + !(caps & NS_HTTP_DISALLOW_SPDY) && + !mConnInfo->UsingHttpProxy() && + gHttpHandler->IsSpdyEnabled()) { + LOG(("nsHttpConnection::Init Setting up SPDY Negotiation")); + nsCOMPtr<nsISupports> securityInfo; + nsresult rv = + mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsISSLSocketControl> ssl = + do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + return; + + nsTArray<nsCString> protocolArray; + protocolArray.AppendElement(NS_LITERAL_CSTRING("spdy/2")); + protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1")); + if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) { + LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK")); + mNPNComplete = false; + } + } + } +} + +void +nsHttpConnection::HandleAlternateProtocol(nsHttpResponseHead *responseHead) +{ + // Look for the Alternate-Protocol header. Alternate-Protocol is + // essentially a way to rediect future transactions from http to + // spdy. + // + + if (!gHttpHandler->IsSpdyEnabled() || mUsingSpdy) + return; + + const char *val = responseHead->PeekHeader(nsHttp::Alternate_Protocol); + if (!val) + return; + + // The spec allows redirections to any port, but due to concerns over + // silently redirecting to stealth ports we only allow port 443 + // + // Alternate-Protocol: 5678:somethingelse, 443:npn-spdy/2 + + if (nsHttp::FindToken(val, "443:npn-spdy/2", HTTP_HEADER_VALUE_SEPS)) { + LOG(("Connection %p Transaction %p found Alternate-Protocol " + "header %s", this, mTransaction.get(), val)); + gHttpHandler->ConnMgr()->ReportSpdyAlternateProtocol(this); + } +} + +nsresult +nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction, + PRInt32 priority) +{ + LOG(("nsHttpConnection::AddTransaction for SPDY")); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSpdySession && mUsingSpdy, + "AddTransaction to live http connection without spdy"); + NS_ABORT_IF_FALSE(mTransaction, + "AddTransaction to idle http connection"); + + if (!mSpdySession->AddStream(httpTransaction, priority)) { + NS_ABORT_IF_FALSE(0, "AddStream should never fail due to" + "RoomForMore() admission check"); + return NS_ERROR_FAILURE; + } + + ResumeSend(httpTransaction); + + return NS_OK; +} + +void nsHttpConnection::Close(nsresult reason) { LOG(("nsHttpConnection::Close [this=%x reason=%x]\n", this, reason)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (NS_FAILED(reason)) { if (mIdleMonitoring) @@ -232,55 +405,86 @@ nsHttpConnection::ProxyStartSSL() if (NS_FAILED(rv)) return rv; nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv); if (NS_FAILED(rv)) return rv; return ssl->ProxyStartSSL(); } +void +nsHttpConnection::DontReuse() +{ + mKeepAliveMask = false; + mKeepAlive = false; + mIdleTimeout = 0; + if (mUsingSpdy) + mSpdySession->DontReuse(); +} + bool nsHttpConnection::CanReuse() { - bool canReuse = IsKeepAlive() && + bool canReuse; + + if (mUsingSpdy) + canReuse = mSpdySession->CanReuse(); + else + canReuse = IsKeepAlive(); + + canReuse = canReuse && (NowInSeconds() - mLastReadTime < mIdleTimeout) && IsAlive(); - + // An idle persistent connection should not have data waiting to be read // before a request is sent. Data here is likely a 408 timeout response // which we would deal with later on through the restart logic, but that // path is more expensive than just closing the socket now. SSL check can // be removed with fixing of 631801 PRUint32 dataSize; - if (canReuse && mSocketIn && !mConnInfo->UsingSSL() && + if (canReuse && mSocketIn && !mConnInfo->UsingSSL() && !mUsingSpdy && NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) { LOG(("nsHttpConnection::CanReuse %p %s" "Socket not reusable because read data pending (%d) on it.\n", this, mConnInfo->Host(), dataSize)); canReuse = false; } return canReuse; } +bool +nsHttpConnection::CanDirectlyActivate() +{ + // return true if a new transaction can be addded to ths connection at any + // time through Activate(). In practice this means this is a healthy SPDY + // connection with room for more concurrent streams. + + return UsingSpdy() && CanReuse() && mSpdySession->RoomForMoreStreams(); +} + PRUint32 nsHttpConnection::TimeToLive() { PRInt32 tmp = mIdleTimeout - (NowInSeconds() - mLastReadTime); if (0 > tmp) tmp = 0; return tmp; } bool nsHttpConnection::IsAlive() { if (!mSocketTransport) return false; + // SocketTransport::IsAlive can run the SSL state machine, so make sure + // the NPN options are set before that happens. + SetupNPN(0); + bool alive; nsresult rv = mSocketTransport->IsAlive(&alive); if (NS_FAILED(rv)) alive = false; //#define TEST_RESTART_LOGIC #ifdef TEST_RESTART_LOGIC if (!alive) { @@ -290,16 +494,20 @@ nsHttpConnection::IsAlive() #endif return alive; } bool nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead) { + // SPDY supports infinite parallelism, so no need to pipeline. + if (mUsingSpdy) + return false; + // XXX there should be a strict mode available that disables this // blacklisting. // assuming connection is HTTP/1.1 with keep-alive enabled if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingSSL()) { // XXX check for bad proxy servers... return true; } @@ -415,30 +623,40 @@ nsHttpConnection::OnHeadersAvailable(nsA // reused as well as the maximum amount of time the connection can be idle // before the server will close it. we ignore the max reuse count, because // a "keep-alive" connection is by definition capable of being reused, and // we only care about being able to reuse it once. if a timeout is not // specified then we use our advertized timeout value. if (mKeepAlive) { val = responseHead->PeekHeader(nsHttp::Keep_Alive); - const char *cp = PL_strcasestr(val, "timeout="); - if (cp) - mIdleTimeout = (PRUint32) atoi(cp + 8); - else - mIdleTimeout = gHttpHandler->IdleTimeout(); + if (!mUsingSpdy) { + const char *cp = PL_strcasestr(val, "timeout="); + if (cp) + mIdleTimeout = (PRUint32) atoi(cp + 8); + else + mIdleTimeout = gHttpHandler->IdleTimeout(); + } + else { + mIdleTimeout = gHttpHandler->SpdyTimeout(); + } LOG(("Connection can be reused [this=%x idle-timeout=%u]\n", this, mIdleTimeout)); } + if (!mProxyConnectStream) + HandleAlternateProtocol(responseHead); + // if we're doing an SSL proxy connect, then we need to check whether or not // the connect was successful. if so, then we have to reset the transaction // and step-up the socket connection to SSL. finally, we have to wake up the // socket write request. if (mProxyConnectStream) { + NS_ABORT_IF_FALSE(!mUsingSpdy, + "SPDY NPN Complete while using proxy connect stream"); mProxyConnectStream = 0; if (responseHead->Status() == 200) { LOG(("proxy CONNECT succeeded! ssl=%s\n", mConnInfo->UsingSSL() ? "true" :"false")); *reset = true; nsresult rv; if (mConnInfo->UsingSSL()) { rv = ProxyStartSSL(); @@ -501,16 +719,18 @@ nsHttpConnection::SetIsReusedAfter(PRUin mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds); } nsresult nsHttpConnection::TakeTransport(nsISocketTransport **aTransport, nsIAsyncInputStream **aInputStream, nsIAsyncOutputStream **aOutputStream) { + if (mUsingSpdy) + return NS_ERROR_FAILURE; if (mTransaction && !mTransaction->IsDone()) return NS_ERROR_IN_PROGRESS; if (!(mSocketTransport && mSocketIn && mSocketOut)) return NS_ERROR_NOT_INITIALIZED; if (mInputOverflow) mSocketIn = mInputOverflow.forget(); @@ -548,31 +768,31 @@ nsHttpConnection::PushBack(const char *d return NS_ERROR_UNEXPECTED; } mInputOverflow = new nsPreloadedStream(mSocketIn, data, length); return NS_OK; } nsresult -nsHttpConnection::ResumeSend() +nsHttpConnection::ResumeSend(nsAHttpTransaction *) { LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mSocketOut) return mSocketOut->AsyncWait(this, 0, 0, nsnull); NS_NOTREACHED("no socket output stream"); return NS_ERROR_UNEXPECTED; } nsresult -nsHttpConnection::ResumeRecv() +nsHttpConnection::ResumeRecv(nsAHttpTransaction *) { LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mSocketIn) return mSocketIn->AsyncWait(this, 0, 0, nsnull); @@ -581,17 +801,18 @@ nsHttpConnection::ResumeRecv() } void nsHttpConnection::BeginIdleMonitoring() { LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(!mTransaction, "BeginIdleMonitoring() while active"); - + NS_ABORT_IF_FALSE(!mUsingSpdy, "Idle monitoring of spdy not allowed"); + LOG(("Entering Idle Monitoring Mode [this=%p]", this)); mIdleMonitoring = true; if (mSocketIn) mSocketIn->AsyncWait(this, 0, 0, nsnull); } void nsHttpConnection::EndIdleMonitoring() @@ -623,16 +844,22 @@ nsHttpConnection::CloseTransaction(nsAHt if (mCurrentBytesRead > mMaxBytesRead) mMaxBytesRead = mCurrentBytesRead; // mask this error code because its not a real error. if (reason == NS_BASE_STREAM_CLOSED) reason = NS_OK; + if (mUsingSpdy) { + DontReuse(); + mUsingSpdy = false; + mSpdySession = nsnull; + } + mTransaction->Close(reason); mTransaction = nsnull; if (mCallbacks) { nsIInterfaceRequestor *cbs = nsnull; mCallbacks.swap(cbs); NS_ProxyRelease(mCallbackTarget, cbs); } @@ -687,30 +914,51 @@ nsHttpConnection::OnSocketWritable() { LOG(("nsHttpConnection::OnSocketWritable [this=%x]\n", this)); nsresult rv; PRUint32 n; bool again = true; do { + mSocketOutCondition = NS_OK; + // if we're doing an SSL proxy connect, then we need to bypass calling // into the transaction. // // NOTE: this code path can't be shared since the transaction doesn't // implement nsIInputStream. doing so is not worth the added cost of // extra indirections during normal reading. // if (mProxyConnectStream) { LOG((" writing CONNECT request stream\n")); rv = mProxyConnectStream->ReadSegments(ReadFromStream, this, nsIOService::gDefaultSegmentSize, &n); } + else if (!EnsureNPNComplete()) { + // When SPDY is disabled this branch is not executed because Activate() + // sets mNPNComplete to true in that case. + + // We are ready to proceed with SSL but the handshake is not done. + // When using NPN to negotiate between HTTPS and SPDY, we need to + // see the results of the handshake to know what bytes to send, so + // we cannot proceed with the request headers. + + rv = NS_OK; + mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK; + n = 0; + } else { + if (gHttpHandler->IsSpdyEnabled() && !mReportedSpdy) { + mReportedSpdy = true; + gHttpHandler->ConnMgr()-> + ReportSpdyConnection(this, mUsingSpdy); + } + LOG((" writing transaction request stream\n")); rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n); } LOG((" ReadSegments returned [rv=%x read=%u sock-cond=%x]\n", rv, n, mSocketOutCondition)); // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. @@ -826,16 +1074,18 @@ nsHttpConnection::OnSocketReadable() nsresult nsHttpConnection::SetupProxyConnect() { const char *val; LOG(("nsHttpConnection::SetupProxyConnect [this=%x]\n", this)); NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED); + NS_ABORT_IF_FALSE(!mUsingSpdy, + "SPDY NPN Complete while using proxy connect stream"); nsCAutoString buf; nsresult rv = nsHttpHandler::GenerateHostPort( nsDependentCString(mConnInfo->Host()), mConnInfo->Port(), buf); if (NS_FAILED(rv)) return rv; // CONNECT host:port HTTP/1.1
--- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -42,16 +42,17 @@ #include "nsHttp.h" #include "nsHttpConnectionInfo.h" #include "nsAHttpConnection.h" #include "nsAHttpTransaction.h" #include "nsXPIDLString.h" #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "prinrval.h" +#include "SpdySession.h" #include "nsIStreamListener.h" #include "nsISocketTransport.h" #include "nsIAsyncInputStream.h" #include "nsIAsyncOutputStream.h" #include "nsIInterfaceRequestor.h" #include "nsIEventTarget.h" @@ -87,35 +88,36 @@ public: // single transaction before it should no longer be kept // alive. a value of 0xffff indicates no limit. nsresult Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime, nsISocketTransport *, nsIAsyncInputStream *, nsIAsyncOutputStream *, nsIInterfaceRequestor *, nsIEventTarget *); // Activate causes the given transaction to be processed on this - // connection. It fails if there is already an existing transaction. - nsresult Activate(nsAHttpTransaction *, PRUint8 caps); + // connection. It fails if there is already an existing transaction unless + // a multiplexing protocol such as SPDY is being used + nsresult Activate(nsAHttpTransaction *, PRUint8 caps, PRInt32 pri); // Close the underlying socket transport. void Close(nsresult reason); //------------------------------------------------------------------------- // XXX document when these are ok to call bool SupportsPipelining() { return mSupportsPipelining; } - bool IsKeepAlive() { return mKeepAliveMask && mKeepAlive; } + bool IsKeepAlive() { return mUsingSpdy || + (mKeepAliveMask && mKeepAlive); } bool CanReuse(); // can this connection be reused? + bool CanDirectlyActivate(); // Returns time in seconds for how long connection can be reused. PRUint32 TimeToLive(); - void DontReuse() { mKeepAliveMask = false; - mKeepAlive = false; - mIdleTimeout = 0; } + void DontReuse(); void DropTransport() { DontReuse(); mSocketTransport = 0; } bool LastTransactionExpectedNoContent() { return mLastTransactionExpectedNoContent; } void SetLastTransactionExpectedNoContent(bool val) @@ -135,43 +137,57 @@ public: nsIAsyncInputStream **, nsIAsyncOutputStream **); void GetSecurityInfo(nsISupports **); bool IsPersistent() { return IsKeepAlive(); } bool IsReused(); void SetIsReusedAfter(PRUint32 afterMilliseconds); void SetIdleTimeout(PRUint16 val) {mIdleTimeout = val;} nsresult PushBack(const char *data, PRUint32 length); - nsresult ResumeSend(); - nsresult ResumeRecv(); + nsresult ResumeSend(nsAHttpTransaction *caller); + nsresult ResumeRecv(nsAHttpTransaction *caller); PRInt64 MaxBytesRead() {return mMaxBytesRead;} static NS_METHOD ReadFromStream(nsIInputStream *, void *, const char *, PRUint32, PRUint32, PRUint32 *); // When a persistent connection is in the connection manager idle // connection pool, the nsHttpConnection still reads errors and hangups // on the socket so that it can be proactively released if the server // initiates a termination. Only call on socket thread. void BeginIdleMonitoring(); void EndIdleMonitoring(); + bool UsingSpdy() { return mUsingSpdy; } + private: // called to cause the underlying socket to start speaking SSL nsresult ProxyStartSSL(); nsresult OnTransactionDone(nsresult reason); nsresult OnSocketWritable(); nsresult OnSocketReadable(); nsresult SetupProxyConnect(); bool IsAlive(); bool SupportsPipelining(nsHttpResponseHead *); + // Makes certain the SSL handshake is complete and NPN negotiation + // has had a chance to happen + bool EnsureNPNComplete(); + void SetupNPN(PRUint8 caps); + + // Inform the connection manager of any SPDY Alternate-Protocol + // redirections + void HandleAlternateProtocol(nsHttpResponseHead *); + + // Directly Add a transaction to an active connection for SPDY + nsresult AddTransaction(nsAHttpTransaction *, PRInt32); + private: nsCOMPtr<nsISocketTransport> mSocketTransport; nsCOMPtr<nsIAsyncInputStream> mSocketIn; nsCOMPtr<nsIAsyncOutputStream> mSocketOut; nsresult mSocketInCondition; nsresult mSocketOutCondition; @@ -199,11 +215,19 @@ private: bool mKeepAlive; bool mKeepAliveMask; bool mSupportsPipelining; bool mIsReused; bool mCompletedProxyConnect; bool mLastTransactionExpectedNoContent; bool mIdleMonitoring; + + // SPDY related + bool mNPNComplete; + bool mSetupNPNCalled; + bool mUsingSpdy; + nsRefPtr<mozilla::net::SpdySession> mSpdySession; + PRInt32 mPriority; + bool mReportedSpdy; }; #endif // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h +++ b/netwerk/protocol/http/nsHttpConnectionInfo.h @@ -119,17 +119,19 @@ public: const char *Host() const { return mHost.get(); } PRInt32 Port() const { return mPort; } nsProxyInfo *ProxyInfo() { return mProxyInfo; } bool UsingHttpProxy() const { return mUsingHttpProxy; } bool UsingSSL() const { return mUsingSSL; } PRInt32 DefaultPort() const { return mUsingSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT; } void SetAnonymous(bool anon) { mHashKey.SetCharAt(anon ? 'A' : '.', 2); } + bool GetAnonymous() { return mHashKey.CharAt(2) == 'A'; } bool ShouldForceConnectMethod(); + const nsCString &GetHost() { return mHost; } private: nsrefcnt mRef; nsCString mHashKey; nsCString mHost; PRInt32 mPort; nsCOMPtr<nsProxyInfo> mProxyInfo; bool mUsingHttpProxy;
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -43,16 +43,19 @@ #include "nsNetCID.h" #include "nsCOMPtr.h" #include "nsNetUtil.h" #include "nsIServiceManager.h" #include "nsIObserverService.h" +#include "nsISSLSocketControl.h" +#include "prnetdb.h" + using namespace mozilla; // defined by the socket transport service while active extern PRThread *gSocketThread; static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); //----------------------------------------------------------------------------- @@ -89,16 +92,17 @@ nsHttpConnectionMgr::nsHttpConnectionMgr , mMaxPersistConnsPerProxy(0) , mIsShuttingDown(false) , mNumActiveConns(0) , mNumIdleConns(0) , mTimeOfNextWakeUp(LL_MAXUINT) { LOG(("Creating nsHttpConnectionMgr @%x\n", this)); mCT.Init(); + mAlternateProtocolHash.Init(16); } nsHttpConnectionMgr::~nsHttpConnectionMgr() { LOG(("Destroying nsHttpConnectionMgr @%x\n", this)); } nsresult @@ -135,16 +139,17 @@ nsHttpConnectionMgr::Init(PRUint16 maxCo PRUint16 maxPersistConnsPerProxy, PRUint16 maxRequestDelay, PRUint16 maxPipelinedRequests) { LOG(("nsHttpConnectionMgr::Init\n")); { ReentrantMonitorAutoEnter mon(mReentrantMonitor); + mSpdyPreferredHash.Init(); mMaxConns = maxConns; mMaxConnsPerHost = maxConnsPerHost; mMaxConnsPerProxy = maxConnsPerProxy; mMaxPersistConnsPerHost = maxPersistConnsPerHost; mMaxPersistConnsPerProxy = maxPersistConnsPerProxy; mMaxRequestDelay = maxRequestDelay; mMaxPipelinedRequests = maxPipelinedRequests; @@ -224,18 +229,23 @@ nsHttpConnectionMgr::PruneDeadConnection mTimeOfNextWakeUp = timeInSeconds + NowInSeconds(); mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT); } else { NS_WARNING("failed to create: timer for pruning the dead connections!"); } } void -nsHttpConnectionMgr::StopPruneDeadConnectionsTimer() +nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() { + // Leave the timer in place if there are connections that potentially + // need management + if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) + return; + LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. mTimeOfNextWakeUp = LL_MAXUINT; if (mTimer) { mTimer->Cancel(); mTimer = NULL; } @@ -384,40 +394,311 @@ nsHttpConnectionMgr::ProcessPendingQ(nsH NS_ADDREF(ci); nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci); if (NS_FAILED(rv)) NS_RELEASE(ci); return rv; } +// Given a nsHttpConnectionInfo find the connection entry object that +// contains either the nshttpconnection or nshttptransaction parameter. +// Normally this is done by the hashkey lookup of connectioninfo, +// but if spdy coalescing is in play it might be found in a redirected +// entry +nsHttpConnectionMgr::nsConnectionEntry * +nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci, + nsHttpConnection *conn, + nsHttpTransaction *trans) +{ + if (!ci) + return nsnull; + + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + + // If there is no sign of coalescing (or it is disabled) then just + // return the primary hash lookup + if (!gHttpHandler->IsSpdyEnabled() || !gHttpHandler->CoalesceSpdy() || + !ent || !ent->mUsingSpdy || ent->mCoalescingKey.IsEmpty()) + return ent; + + // If there is no preferred coalescing entry for this host (or the + // preferred entry is the one that matched the mCT hash lookup) then + // there is only option + nsConnectionEntry *preferred = mSpdyPreferredHash.Get(ent->mCoalescingKey); + if (!preferred || (preferred == ent)) + return ent; + + if (conn) { + // The connection could be either in preferred or ent. It is most + // likely the only active connection in preferred - so start with that. + if (preferred->mActiveConns.Contains(conn)) + return preferred; + if (preferred->mIdleConns.Contains(conn)) + return preferred; + } + + if (trans && preferred->mPendingQ.Contains(trans)) + return preferred; + + // Neither conn nor trans found in preferred, use the default entry + return ent; +} + nsresult nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", this, conn)); - nsHttpConnectionInfo *ci = conn->ConnectionInfo(); - if (!ci) + if (!conn->ConnectionInfo()) return NS_ERROR_UNEXPECTED; - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), + conn, nsnull); if (!ent || !ent->mIdleConns.RemoveElement(conn)) return NS_ERROR_UNEXPECTED; conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); mNumIdleConns--; - if (0 == mNumIdleConns) - StopPruneDeadConnectionsTimer(); + ConditionallyStopPruneDeadConnectionsTimer(); return NS_OK; } +void +nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn, + bool usingSpdy) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), + conn, nsnull); + + NS_ABORT_IF_FALSE(ent, "no connection entry"); + if (!ent) + return; + + ent->mTestedSpdy = true; + + if (!usingSpdy) { + if (ent->mUsingSpdy) + conn->DontReuse(); + return; + } + + ent->mUsingSpdy = true; + + PRUint32 ttl = conn->TimeToLive(); + PRUint64 timeOfExpire = NowInSeconds() + ttl; + if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) + PruneDeadConnectionsAfter(ttl); + + // Lookup preferred directly from the hash instead of using + // GetSpdyPreferred() because we want to avoid the cert compatibility + // check at this point because the cert is never part of the hash + // lookup. Filtering on that has to be done at the time of use + // rather than the time of registration (i.e. now). + nsConnectionEntry *preferred = + mSpdyPreferredHash.Get(ent->mCoalescingKey); + + LOG(("ReportSpdyConnection %s %s ent=%p ispreferred=%d\n", + ent->mConnInfo->Host(), ent->mCoalescingKey.get(), + ent, preferred)); + + if (!preferred) { + ent->mSpdyPreferred = true; + SetSpdyPreferred(ent); + preferred = ent; + } + else if (preferred != ent) { + // A different hostname is the preferred spdy host for this + // IP address. + ent->mUsingSpdy = true; + conn->DontReuse(); + } + + ProcessSpdyPendingQ(); +} + +bool +nsHttpConnectionMgr::GetSpdyAlternateProtocol(nsACString &hostPortKey) +{ + // The Alternate Protocol hash is protected under the monitor because + // it is read from both the main and the network thread. + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + return mAlternateProtocolHash.Contains(hostPortKey); +} + +void +nsHttpConnectionMgr::ReportSpdyAlternateProtocol(nsHttpConnection *conn) +{ + // Check network.http.spdy.use-alternate-protocol pref + if (!gHttpHandler->UseAlternateProtocol()) + return; + + // For now lets not bypass proxies due to the alternate-protocol header + if (conn->ConnectionInfo()->UsingHttpProxy()) + return; + + nsCString hostPortKey(conn->ConnectionInfo()->Host()); + if (conn->ConnectionInfo()->Port() != 80) { + hostPortKey.Append(NS_LITERAL_CSTRING(":")); + hostPortKey.AppendInt(conn->ConnectionInfo()->Port()); + } + + // The Alternate Protocol hash is protected under the monitor because + // it is read from both the main and the network thread. + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // Check to see if this is already present + if (mAlternateProtocolHash.Contains(hostPortKey)) + return; + + if (mAlternateProtocolHash.mHashTable.entryCount > 2000) + PL_DHashTableEnumerate(&mAlternateProtocolHash.mHashTable, + &nsHttpConnectionMgr::TrimAlternateProtocolHash, + this); + + mAlternateProtocolHash.Put(hostPortKey); +} + +void +nsHttpConnectionMgr::RemoveSpdyAlternateProtocol(nsACString &hostPortKey) +{ + // The Alternate Protocol hash is protected under the monitor because + // it is read from both the main and the network thread. + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + return mAlternateProtocolHash.Remove(hostPortKey); +} + +PLDHashOperator +nsHttpConnectionMgr::TrimAlternateProtocolHash(PLDHashTable *table, + PLDHashEntryHdr *hdr, + PRUint32 number, + void *closure) +{ + nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; + + if (self->mAlternateProtocolHash.mHashTable.entryCount > 2000) + return PL_DHASH_REMOVE; + return PL_DHASH_STOP; +} + +nsHttpConnectionMgr::nsConnectionEntry * +nsHttpConnectionMgr::GetSpdyPreferred(nsConnectionEntry *aOriginalEntry) +{ + if (!gHttpHandler->IsSpdyEnabled() || + !gHttpHandler->CoalesceSpdy() || + aOriginalEntry->mCoalescingKey.IsEmpty()) + return nsnull; + + nsConnectionEntry *preferred = + mSpdyPreferredHash.Get(aOriginalEntry->mCoalescingKey); + + // if there is no redirection no cert validation is required + if (preferred == aOriginalEntry) + return aOriginalEntry; + + // if there is no preferred host or it is no longer using spdy + // then skip pooling + if (!preferred || !preferred->mUsingSpdy) + return nsnull; + + // if there is not an active spdy session in this entry then + // we cannot pool because the cert upon activation may not + // be the same as the old one. Active sessions are prohibited + // from changing certs. + + nsHttpConnection *activeSpdy = nsnull; + + for (PRUint32 index = 0; index < preferred->mActiveConns.Length(); ++index) { + if (preferred->mActiveConns[index]->CanDirectlyActivate()) { + activeSpdy = preferred->mActiveConns[index]; + break; + } + } + + if (!activeSpdy) { + // remove the preferred status of this entry if it cannot be + // used for pooling. + preferred->mSpdyPreferred = false; + RemoveSpdyPreferred(preferred->mCoalescingKey); + LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " + "preferred host mapping %s to %s removed due to inactivity.\n", + aOriginalEntry->mConnInfo->Host(), + preferred->mConnInfo->Host())); + + return nsnull; + } + + // Check that the server cert supports redirection + nsresult rv; + bool isJoined = false; + + nsCOMPtr<nsISupports> securityInfo; + nsCOMPtr<nsISSLSocketControl> sslSocketControl; + nsCAutoString negotiatedNPN; + + activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (!securityInfo) + return nsnull; + + sslSocketControl = do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + return nsnull; + + rv = sslSocketControl->JoinConnection(NS_LITERAL_CSTRING("spdy/2"), + aOriginalEntry->mConnInfo->GetHost(), + aOriginalEntry->mConnInfo->Port(), + &isJoined); + + if (NS_FAILED(rv) || !isJoined) { + LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " + "Host %s cannot be confirmed to be joined " + "with %s connections", + preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host())); + return nsnull; + } + + // IP pooling confirmed + LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " + "Host %s has cert valid for %s connections", + preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host())); + return preferred; +} + +void +nsHttpConnectionMgr::SetSpdyPreferred(nsConnectionEntry *ent) +{ + if (!gHttpHandler->CoalesceSpdy()) + return; + + if (ent->mCoalescingKey.IsEmpty()) + return; + + mSpdyPreferredHash.Put(ent->mCoalescingKey, ent); +} + +void +nsHttpConnectionMgr::RemoveSpdyPreferred(nsACString &aHashKey) +{ + if (!gHttpHandler->CoalesceSpdy()) + return; + + if (aHashKey.IsEmpty()) + return; + + mSpdyPreferredHash.Remove(aHashKey); +} + //----------------------------------------------------------------------------- // enumeration callbacks PLDHashOperator nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key, nsAutoPtr<nsConnectionEntry> &ent, void *closure) { @@ -444,79 +725,101 @@ nsHttpConnectionMgr::PurgeExcessIdleConn // There are no idle conns left in this connection entry return PL_DHASH_NEXT; } nsHttpConnection *conn = ent->mIdleConns[0]; ent->mIdleConns.RemoveElementAt(0); conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); self->mNumIdleConns--; - if (0 == self->mNumIdleConns) - self->StopPruneDeadConnectionsTimer(); + self->ConditionallyStopPruneDeadConnectionsTimer(); } return PL_DHASH_STOP; } PLDHashOperator nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, nsAutoPtr<nsConnectionEntry> &ent, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get())); // Find out how long it will take for next idle connection to not be reusable // anymore. + bool liveConnections = false; PRUint32 timeToNextExpire = PR_UINT32_MAX; PRInt32 count = ent->mIdleConns.Length(); if (count > 0) { for (PRInt32 i=count-1; i>=0; --i) { nsHttpConnection *conn = ent->mIdleConns[i]; if (!conn->CanReuse()) { ent->mIdleConns.RemoveElementAt(i); conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); self->mNumIdleConns--; } else { timeToNextExpire = NS_MIN(timeToNextExpire, conn->TimeToLive()); + liveConnections = true; } } } + if (ent->mUsingSpdy) { + for (PRUint32 index = 0; index < ent->mActiveConns.Length(); ++index) { + nsHttpConnection *conn = ent->mActiveConns[index]; + if (conn->UsingSpdy()) { + if (!conn->CanReuse()) { + // marking it dont reuse will create an active tear down if + // the spdy session is idle. + conn->DontReuse(); + } + else { + timeToNextExpire = NS_MIN(timeToNextExpire, + conn->TimeToLive()); + liveConnections = true; + } + } + } + } + // If time to next expire found is shorter than time to next wake-up, we need to // change the time for next wake-up. - PRUint32 now = NowInSeconds(); - if (0 < ent->mIdleConns.Length()) { + if (liveConnections) { + PRUint32 now = NowInSeconds(); PRUint64 timeOfNextExpire = now + timeToNextExpire; // If pruning of dead connections is not already scheduled to happen // or time found for next connection to expire is is before // mTimeOfNextWakeUp, we need to schedule the pruning to happen // after timeToNextExpire. if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) { self->PruneDeadConnectionsAfter(timeToNextExpire); } - } else if (0 == self->mNumIdleConns) { - self->StopPruneDeadConnectionsTimer(); + } else { + self->ConditionallyStopPruneDeadConnectionsTimer(); } #ifdef DEBUG count = ent->mActiveConns.Length(); if (count > 0) { for (PRInt32 i=count-1; i>=0; --i) { nsHttpConnection *conn = ent->mActiveConns[i]; LOG((" active conn [%x] with trans [%x]\n", conn, conn->Transaction())); } } #endif // if this entry is empty, then we can remove it. if (ent->mIdleConns.Length() == 0 && ent->mActiveConns.Length() == 0 && ent->mHalfOpens.Length() == 0 && - ent->mPendingQ.Length() == 0) { + ent->mPendingQ.Length() == 0 && + ((!ent->mTestedSpdy && !ent->mUsingSpdy) || + !gHttpHandler->IsSpdyEnabled() || + self->mCT.Count() > 300)) { LOG((" removing empty connection entry\n")); return PL_DHASH_REMOVE; } // else, use this opportunity to compact our arrays... ent->mIdleConns.Compact(); ent->mActiveConns.Compact(); ent->mPendingQ.Compact(); @@ -552,18 +855,17 @@ nsHttpConnectionMgr::ShutdownPassCB(cons ent->mIdleConns.RemoveElementAt(0); self->mNumIdleConns--; conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); } // If all idle connections are removed, // we can stop pruning dead connections. - if (0 == self->mNumIdleConns) - self->StopPruneDeadConnectionsTimer(); + self->ConditionallyStopPruneDeadConnectionsTimer(); // close all pending transactions while (ent->mPendingQ.Length()) { trans = ent->mPendingQ[0]; ent->mPendingQ.RemoveElementAt(0); trans->Close(NS_ERROR_ABORT); @@ -577,25 +879,29 @@ nsHttpConnectionMgr::ShutdownPassCB(cons return PL_DHASH_REMOVE; } //----------------------------------------------------------------------------- bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n", ent->mConnInfo->HashKey().get())); - PRInt32 i, count = ent->mPendingQ.Length(); + if (gHttpHandler->IsSpdyEnabled()) + ProcessSpdyPendingQ(ent); + + PRUint32 i, count = ent->mPendingQ.Length(); if (count > 0) { LOG((" pending-count=%u\n", count)); nsHttpTransaction *trans = nsnull; nsHttpConnection *conn = nsnull; - for (i=0; i<count; ++i) { + for (i = 0; i < count; ++i) { trans = ent->mPendingQ[i]; // When this transaction has already established a half-open // connection, we want to prevent any duplicate half-open // connections from being established and bound to this // transaction. Allow only use of an idle persistent connection // (if found) for transactions referred by a half-open connection. bool alreadyHalfOpen = false; @@ -604,16 +910,23 @@ nsHttpConnectionMgr::ProcessPendingQForE alreadyHalfOpen = true; break; } } GetConnection(ent, trans, alreadyHalfOpen, &conn); if (conn) break; + + // Check to see if a pending transaction was dispatched with the + // coalesce logic + if (count != ent->mPendingQ.Length()) { + count = ent->mPendingQ.Length(); + i = 0; + } } if (conn) { LOG((" dispatching pending transaction...\n")); // remove pending transaction ent->mPendingQ.RemoveElementAt(i); nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn); @@ -731,26 +1044,37 @@ void nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, nsHttpTransaction *trans, bool onlyReusedConnection, nsHttpConnection **result) { LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n", ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps()))); - // First, see if an idle persistent connection may be reused instead of + // First, see if an existing connection can be used - either an idle + // persistent connection or an active spdy session may be reused instead of // establishing a new socket. We do not need to check the connection limits // yet as they govern the maximum number of open connections and reusing // an old connection never increases that. *result = nsnull; nsHttpConnection *conn = nsnull; + bool addConnToActiveList = true; if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) { + + // if willing to use spdy look for an active spdy connections + // before considering idle http ones. + if (gHttpHandler->IsSpdyEnabled()) { + conn = GetSpdyPreferredConn(ent); + if (conn) + addConnToActiveList = false; + } + // search the idle connection list. Each element in the list // has a reference, so if we remove it from the list into a local // ptr, that ptr now owns the reference while (!conn && (ent->mIdleConns.Length() > 0)) { conn = ent->mIdleConns[0]; // we check if the connection can be reused before even checking if // it is a "matching" connection. if (!conn->CanReuse()) { @@ -763,28 +1087,40 @@ nsHttpConnectionMgr::GetConnection(nsCon conn->EndIdleMonitoring(); } ent->mIdleConns.RemoveElementAt(0); mNumIdleConns--; // If there are no idle connections left at all, we need to make // sure that we are not pruning dead connections anymore. - if (0 == mNumIdleConns) - StopPruneDeadConnectionsTimer(); + ConditionallyStopPruneDeadConnectionsTimer(); } } if (!conn) { // If the onlyReusedConnection parameter is TRUE, then GetConnection() // does not create new transports under any circumstances. if (onlyReusedConnection) return; + if (gHttpHandler->IsSpdyEnabled() && + ent->mConnInfo->UsingSSL() && + !ent->mConnInfo->UsingHttpProxy()) + { + // If this is a possible Spdy connection we need to limit the number + // of connections outstanding to 1 while we wait for the spdy/https + // ReportSpdyConnection() + + if ((!ent->mTestedSpdy || ent->mUsingSpdy) && + (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) + return; + } + // Check if we need to purge an idle connection. Note that we may have // removed one above; if so, this will be a no-op. We do this before // checking the active connection limit to catch the case where we do // have an idle connection, but the purge timer hasn't fired yet. // XXX this just purges a random idle connection. we should instead // enumerate the entire hash table to find the eldest idle connection. if (mNumIdleConns && mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) mCT.Enumerate(PurgeExcessIdleConnectionsCB, this); @@ -794,27 +1130,34 @@ nsHttpConnectionMgr::GetConnection(nsCon // host or proxy. If we have, we're done. if (AtActiveConnectionLimit(ent, trans->Caps())) { LOG(("nsHttpConnectionMgr::GetConnection [ci = %s]" "at active connection limit - will queue\n", ent->mConnInfo->HashKey().get())); return; } + LOG(("nsHttpConnectionMgr::GetConnection Open Connection " + "%s %s ent=%p spdy=%d", + ent->mConnInfo->Host(), ent->mCoalescingKey.get(), + ent, ent->mUsingSpdy)); + nsresult rv = CreateTransport(ent, trans); if (NS_FAILED(rv)) trans->Close(rv); return; } - // hold an owning ref to this connection - ent->mActiveConns.AppendElement(conn); - mNumActiveConns++; + if (addConnToActiveList) { + // hold an owning ref to this connection + ent->mActiveConns.AppendElement(conn); + mNumActiveConns++; + } + NS_ADDREF(conn); - *result = conn; } void nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn, nsConnectionEntry *ent) { NS_ADDREF(conn); @@ -845,39 +1188,54 @@ nsHttpConnectionMgr::CreateTransport(nsC NS_ENSURE_SUCCESS(rv, rv); ent->mHalfOpens.AppendElement(sock); return NS_OK; } nsresult nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, - nsAHttpTransaction *trans, + nsHttpTransaction *aTrans, PRUint8 caps, nsHttpConnection *conn) { LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n", - ent->mConnInfo->HashKey().get(), trans, caps, conn)); + ent->mConnInfo->HashKey().get(), aTrans, caps, conn)); + nsresult rv; + + PRInt32 priority = aTrans->Priority(); + + if (conn->UsingSpdy()) { + LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s," + "Connection host = %s\n", + aTrans->ConnectionInfo()->Host(), + conn->ConnectionInfo()->Host())); + rv = conn->Activate(aTrans, caps, priority); + NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch"); + return rv; + } nsConnectionHandle *handle = new nsConnectionHandle(conn); if (!handle) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(handle); nsHttpPipeline *pipeline = nsnull; + nsAHttpTransaction *trans = aTrans; + if (conn->SupportsPipelining() && (caps & NS_HTTP_ALLOW_PIPELINING)) { LOG((" looking to build pipeline...\n")); if (BuildPipeline(ent, trans, &pipeline)) trans = pipeline; } // give the transaction the indirect reference to the connection. trans->SetConnection(handle); - nsresult rv = conn->Activate(trans, caps); + rv = conn->Activate(trans, caps, priority); if (NS_FAILED(rv)) { LOG((" conn->Activate failed [rv=%x]\n", rv)); ent->mActiveConns.RemoveElement(conn); mNumActiveConns--; // sever back references to connection, and do so without triggering // a call to ReclaimConnection ;-) trans->SetConnection(nsnull); @@ -962,16 +1320,27 @@ nsHttpConnectionMgr::ProcessNewTransacti if (!clone) return NS_ERROR_OUT_OF_MEMORY; ent = new nsConnectionEntry(clone); if (!ent) return NS_ERROR_OUT_OF_MEMORY; mCT.Put(ci->HashKey(), ent); } + // SPDY coalescing of hostnames means we might redirect from this + // connection entry onto the preferred one. + nsConnectionEntry *preferredEntry = GetSpdyPreferred(ent); + if (preferredEntry && (preferredEntry != ent)) { + LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " + "redirected via coalescing from %s to %s\n", trans, + ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host())); + + ent = preferredEntry; + } + // If we are doing a force reload then close out any existing conns // to this host so that changes in DNS, LBs, etc.. are reflected if (caps & NS_HTTP_CLEAR_KEEPALIVES) ClosePersistentConnections(ent); // Check if the transaction already has a sticky reference to a connection. // If so, then we can just use it directly by transferring its reference // to the new connection var instead of calling GetConnection() to search @@ -1001,16 +1370,92 @@ nsHttpConnectionMgr::ProcessNewTransacti else { rv = DispatchTransaction(ent, trans, caps, conn); NS_RELEASE(conn); } return rv; } +// This function tries to dispatch the pending spdy transactions on +// the connection entry sent in as an argument. It will do so on the +// active spdy connection either in that same entry or in the +// redirected 'preferred' entry for the same coalescing hash key if +// coalescing is enabled. + +void +nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent) +{ + nsHttpConnection *conn = GetSpdyPreferredConn(ent); + if (!conn) + return; + + for (PRInt32 index = ent->mPendingQ.Length() - 1; + index >= 0 && conn->CanDirectlyActivate(); + --index) { + nsHttpTransaction *trans = ent->mPendingQ[index]; + + if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) || + trans->Caps() & NS_HTTP_DISALLOW_SPDY) + continue; + + ent->mPendingQ.RemoveElementAt(index); + + nsresult rv2 = DispatchTransaction(ent, trans, trans->Caps(), conn); + NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv2), "Dispatch SPDY Transaction"); + NS_RELEASE(trans); + } +} + +PLDHashOperator +nsHttpConnectionMgr::ProcessSpdyPendingQCB(const nsACString &key, + nsAutoPtr<nsConnectionEntry> &ent, + void *closure) +{ + nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; + self->ProcessSpdyPendingQ(ent); + return PL_DHASH_NEXT; +} + +void +nsHttpConnectionMgr::ProcessSpdyPendingQ() +{ + mCT.Enumerate(ProcessSpdyPendingQCB, this); +} + +nsHttpConnection * +nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(ent, "no connection entry"); + + nsConnectionEntry *preferred = GetSpdyPreferred(ent); + + // this entry is spdy-enabled if it is involved in a redirect + if (preferred) + ent->mUsingSpdy = true; + else + preferred = ent; + + nsHttpConnection *conn = nsnull; + + if (preferred->mUsingSpdy) { + for (PRUint32 index = 0; + index < preferred->mActiveConns.Length(); + ++index) { + if (preferred->mActiveConns[index]->CanDirectlyActivate()) { + conn = preferred->mActiveConns[index]; + break; + } + } + } + + return conn; +} + //----------------------------------------------------------------------------- void nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *) { LOG(("nsHttpConnectionMgr::OnMsgShutdown\n")); mCT.Enumerate(ShutdownPassCB, this); @@ -1036,18 +1481,19 @@ nsHttpConnectionMgr::OnMsgNewTransaction void nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param) { LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param)); nsHttpTransaction *trans = (nsHttpTransaction *) param; trans->SetPriority(priority); - nsHttpConnectionInfo *ci = trans->ConnectionInfo(); - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(), + nsnull, trans); + if (ent) { PRInt32 index = ent->mPendingQ.IndexOf(trans); if (index >= 0) { ent->mPendingQ.RemoveElementAt(index); InsertTransactionSorted(ent->mPendingQ, trans); } } @@ -1064,18 +1510,19 @@ nsHttpConnectionMgr::OnMsgCancelTransact // if the transaction owns a connection and the transaction is not done, // then ask the connection to close the transaction. otherwise, close the // transaction directly (removing it from the pending queue first). // nsAHttpConnection *conn = trans->Connection(); if (conn && !trans->IsDone()) conn->CloseTransaction(trans, reason); else { - nsHttpConnectionInfo *ci = trans->ConnectionInfo(); - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(), + nsnull, trans); + if (ent) { PRInt32 index = ent->mPendingQ.IndexOf(trans); if (index >= 0) { ent->mPendingQ.RemoveElementAt(index); nsHttpTransaction *temp = trans; NS_RELEASE(temp); // b/c NS_RELEASE nulls its argument! } } @@ -1104,17 +1551,20 @@ nsHttpConnectionMgr::OnMsgProcessPending void nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *) { LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. mTimeOfNextWakeUp = LL_MAXUINT; - if (mNumIdleConns > 0) + + // check canreuse() for all idle connections plus any active connections on + // connection entries that are using spdy. + if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) mCT.Enumerate(PruneDeadConnectionsCB, this); } void nsHttpConnectionMgr::OnMsgClosePersistentConnections(PRInt32, void *) { LOG(("nsHttpConnectionMgr::OnMsgClosePersistentConnections\n")); @@ -1129,27 +1579,36 @@ nsHttpConnectionMgr::OnMsgReclaimConnect nsHttpConnection *conn = (nsHttpConnection *) param; // // 1) remove the connection from the active list // 2) if keep-alive, add connection to idle list // 3) post event to process the pending transaction queue // - nsHttpConnectionInfo *ci = conn->ConnectionInfo(); - NS_ADDREF(ci); + nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), + conn, nsnull); + nsHttpConnectionInfo *ci = nsnull; - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (!ent) { + // this should never happen + NS_ASSERTION(ent, "no connection entry"); + NS_ADDREF(ci = conn->ConnectionInfo()); + } + else { + NS_ADDREF(ci = ent->mConnInfo); - NS_ASSERTION(ent, "no connection entry"); - if (ent) { // If the connection is in the active list, remove that entry // and the reference held by the mActiveConns list. // This is never the final reference on conn as the event context // is also holding one that is released at the end of this function. + + if (ent->mUsingSpdy) + conn->DontReuse(); + if (ent->mActiveConns.RemoveElement(conn)) { nsHttpConnection *temp = conn; NS_RELEASE(temp); mNumActiveConns--; } if (conn->CanReuse()) { LOG((" adding connection to idle list\n")); @@ -1168,17 +1627,17 @@ nsHttpConnectionMgr::OnMsgReclaimConnect } NS_ADDREF(conn); ent->mIdleConns.InsertElementAt(idx, conn); mNumIdleConns++; conn->BeginIdleMonitoring(); // If the added connection was first idle connection or has shortest - // time to live among the idle connections, pruning dead + // time to live among the watched connections, pruning dead // connections needs to be done when it can't be reused anymore. PRUint32 timeToLive = conn->TimeToLive(); if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) PruneDeadConnectionsAfter(timeToLive); } else { LOG((" connection cannot be reused; closing connection\n")); // make sure the connection is closed and release our reference. @@ -1218,16 +1677,25 @@ nsHttpConnectionMgr::OnMsgUpdateParam(PR case MAX_PIPELINED_REQUESTS: mMaxPipelinedRequests = value; break; default: NS_NOTREACHED("unexpected parameter name"); } } +// nsHttpConnectionMgr::nsConnectionEntry +nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry() +{ + if (mSpdyPreferred) + gHttpHandler->ConnMgr()->RemoveSpdyPreferred(mCoalescingKey); + + NS_RELEASE(mConnInfo); +} + //----------------------------------------------------------------------------- // nsHttpConnectionMgr::nsConnectionHandle nsHttpConnectionMgr::nsConnectionHandle::~nsConnectionHandle() { if (mConn) { gHttpHandler->ReclaimConnection(mConn); NS_RELEASE(mConn); @@ -1241,25 +1709,25 @@ nsHttpConnectionMgr::nsConnectionHandle: nsHttpRequestHead *req, nsHttpResponseHead *resp, bool *reset) { return mConn->OnHeadersAvailable(trans, req, resp, reset); } nsresult -nsHttpConnectionMgr::nsConnectionHandle::ResumeSend() +nsHttpConnectionMgr::nsConnectionHandle::ResumeSend(nsAHttpTransaction *caller) { - return mConn->ResumeSend(); + return mConn->ResumeSend(caller); } nsresult -nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv() +nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv(nsAHttpTransaction *caller) { - return mConn->ResumeRecv(); + return mConn->ResumeRecv(caller); } void nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) { mConn->CloseTransaction(trans, reason); } @@ -1411,20 +1879,24 @@ nsHalfOpenSocket::SetupStreams(nsISocket gHttpHandler->ConnMgr()->StartedConnect(); return rv; } nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() { - nsresult rv = SetupStreams(getter_AddRefs(mSocketTransport), - getter_AddRefs(mStreamIn), - getter_AddRefs(mStreamOut), - false); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + + rv = SetupStreams(getter_AddRefs(mSocketTransport), + getter_AddRefs(mStreamIn), + getter_AddRefs(mStreamOut), + false); LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]", this, mEnt->mConnInfo->Host(), rv)); if (NS_FAILED(rv)) { if (mStreamOut) mStreamOut->AsyncWait(nsnull, 0, 0, nsnull); mStreamOut = nsnull; mStreamIn = nsnull; mSocketTransport = nsnull; @@ -1451,16 +1923,17 @@ nsHttpConnectionMgr::nsHalfOpenSocket::S return rv; } void nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer() { PRUint16 timeout = gHttpHandler->GetIdleSynTimeout(); NS_ABORT_IF_FALSE(!mSynTimer, "timer already initd"); + if (timeout) { // Setup the timer that will establish a backup socket // if we do not get a writable event on the main one. // We do this because a lost SYN takes a very long time // to repair at the TCP level. // // Failure to setup the timer is something we can live with, // so don't return an error in that case. @@ -1616,16 +2089,52 @@ nsHalfOpenSocket::OnOutputStreamReady(ns // method for nsITransportEventSink NS_IMETHODIMP nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans, nsresult status, PRUint64 progress, PRUint64 progressMax) { + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // if we are doing spdy coalescing and haven't recorded the ip address + // for this entry before then make the hash key if our dns lookup + // just completed + + if (gHttpHandler->IsSpdyEnabled() && + gHttpHandler->CoalesceSpdy() && + mEnt && mEnt->mConnInfo && mEnt->mConnInfo->UsingSSL() && + !mEnt->mConnInfo->UsingHttpProxy() && + mEnt->mCoalescingKey.IsEmpty() && + status == nsISocketTransport::STATUS_CONNECTED_TO) { + + PRNetAddr addr; + nsresult rv = mSocketTransport->GetPeerAddr(&addr); + if (NS_SUCCEEDED(rv)) { + mEnt->mCoalescingKey.SetCapacity(72); + PR_NetAddrToString(&addr, mEnt->mCoalescingKey.BeginWriting(), 64); + mEnt->mCoalescingKey.SetLength( + strlen(mEnt->mCoalescingKey.BeginReading())); + + if (mEnt->mConnInfo->GetAnonymous()) + mEnt->mCoalescingKey.AppendLiteral("~A:"); + else + mEnt->mCoalescingKey.AppendLiteral("~.:"); + mEnt->mCoalescingKey.AppendInt(mEnt->mConnInfo->Port()); + + LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus " + "STATUS_CONNECTED_TO Established New Coalescing Key for host " + "%s [%s]", mEnt->mConnInfo->Host(), + mEnt->mCoalescingKey.get())); + + gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt); + } + } + if (mTransaction) mTransaction->OnTransportStatus(trans, status, progress); if (trans != mSocketTransport) return NS_OK; switch (status) { case nsISocketTransport::STATUS_CONNECTING_TO:
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -40,22 +40,25 @@ #define nsHttpConnectionMgr_h__ #include "nsHttpConnectionInfo.h" #include "nsHttpConnection.h" #include "nsHttpTransaction.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include "nsClassHashtable.h" +#include "nsDataHashtable.h" #include "nsAutoPtr.h" #include "mozilla/ReentrantMonitor.h" #include "nsISocketTransportService.h" +#include "nsHashSets.h" #include "nsIObserver.h" #include "nsITimer.h" +#include "nsIX509Cert3.h" class nsHttpPipeline; //----------------------------------------------------------------------------- class nsHttpConnectionMgr : public nsIObserver { public: @@ -91,18 +94,19 @@ public: //------------------------------------------------------------------------- // NOTE: functions below may be called on any thread. //------------------------------------------------------------------------- // Schedules next pruning of dead connection to happen after // given time. void PruneDeadConnectionsAfter(PRUint32 time); - // Stops timer scheduled for next pruning of dead connections. - void StopPruneDeadConnectionsTimer(); + // Stops timer scheduled for next pruning of dead connections if + // there are no more idle connections or active spdy ones + void ConditionallyStopPruneDeadConnectionsTimer(); // adds a transaction to the list of managed transactions. nsresult AddTransaction(nsHttpTransaction *, PRInt32 priority); // called to reschedule the given transaction. it must already have been // added to the connection manager via AddTransaction. nsresult RescheduleTransaction(nsHttpTransaction *, PRInt32 priority); @@ -125,16 +129,21 @@ public: // connection can be reused then it will be added to the idle list, else // it will be closed. nsresult ReclaimConnection(nsHttpConnection *conn); // called to update a parameter after the connection manager has already // been initialized. nsresult UpdateParam(nsParamName name, PRUint16 value); + // Lookup/Cancel HTTP->SPDY redirections + bool GetSpdyAlternateProtocol(nsACString &key); + void ReportSpdyAlternateProtocol(nsHttpConnection *); + void RemoveSpdyAlternateProtocol(nsACString &key); + //------------------------------------------------------------------------- // NOTE: functions below may be called only on the socket thread. //------------------------------------------------------------------------- // removes the next transaction for the specified connection from the // pending transaction queue. void AddTransactionToPipeline(nsHttpPipeline *); @@ -142,40 +151,67 @@ public: // preference to the specified connection. nsresult ProcessPendingQ(nsHttpConnectionInfo *); // This is used to force an idle connection to be closed and removed from // the idle connection list. It is called when the idle connection detects // that the network peer has closed the transport. nsresult CloseIdleConnection(nsHttpConnection *); + // The connection manager needs to know when a normal HTTP connection has been + // upgraded to SPDY because the dispatch and idle semantics are a little + // bit different. + void ReportSpdyConnection(nsHttpConnection *, bool usingSpdy); + private: virtual ~nsHttpConnectionMgr(); class nsHalfOpenSocket; // nsConnectionEntry // // mCT maps connection info hash key to nsConnectionEntry object, which // contains list of active and idle connections as well as the list of // pending transactions. // struct nsConnectionEntry { nsConnectionEntry(nsHttpConnectionInfo *ci) - : mConnInfo(ci) + : mConnInfo(ci), + mUsingSpdy(false), + mTestedSpdy(false), + mSpdyPreferred(false) { NS_ADDREF(mConnInfo); } - ~nsConnectionEntry() { NS_RELEASE(mConnInfo); } + ~nsConnectionEntry(); nsHttpConnectionInfo *mConnInfo; nsTArray<nsHttpTransaction*> mPendingQ; // pending transaction queue nsTArray<nsHttpConnection*> mActiveConns; // active connections nsTArray<nsHttpConnection*> mIdleConns; // idle persistent connections nsTArray<nsHalfOpenSocket*> mHalfOpens; + + // Spdy sometimes resolves the address in the socket manager in order + // to re-coalesce sharded HTTP hosts. The dotted decimal address is + // combined with the Anonymous flag from the connection information + // to build the hash key for hosts in the same ip pool. + // + // When a set of hosts are coalesced together one of them is marked + // mSpdyPreferred. The mapping is maintained in the connection mananger + // mSpdyPreferred hash. + // + nsCString mCoalescingKey; + + // To have the UsingSpdy flag means some host with the same hash information + // has done NPN=spdy/2 at some point. It does not mean every connection + // is currently using spdy. + bool mUsingSpdy; + + bool mTestedSpdy; + bool mSpdyPreferred; }; // nsConnectionHandle // // thin wrapper around a real connection, used to keep track of references // to the connection to determine when the connection may be reused. the // transaction (or pipeline) owns a reference to this handle. this extra // layer of indirection greatly simplifies consumer code, avoiding the @@ -268,27 +304,43 @@ private: static PLDHashOperator PruneDeadConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *); static PLDHashOperator ShutdownPassCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *); static PLDHashOperator PurgeExcessIdleConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *); static PLDHashOperator ClosePersistentConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *); bool ProcessPendingQForEntry(nsConnectionEntry *); bool AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps); void GetConnection(nsConnectionEntry *, nsHttpTransaction *, bool, nsHttpConnection **); - nsresult DispatchTransaction(nsConnectionEntry *, nsAHttpTransaction *, + nsresult DispatchTransaction(nsConnectionEntry *, nsHttpTransaction *, PRUint8 caps, nsHttpConnection *); bool BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **); nsresult ProcessNewTransaction(nsHttpTransaction *); nsresult EnsureSocketThreadTargetIfOnline(); void ClosePersistentConnections(nsConnectionEntry *ent); nsresult CreateTransport(nsConnectionEntry *, nsHttpTransaction *); void AddActiveConn(nsHttpConnection *, nsConnectionEntry *); void StartedConnect(); void RecvdConnect(); - + + // Manage the preferred spdy connection entry for this address + nsConnectionEntry *GetSpdyPreferred(nsConnectionEntry *aOriginalEntry); + void SetSpdyPreferred(nsConnectionEntry *ent); + void RemoveSpdyPreferred(nsACString &aDottedDecimal); + nsHttpConnection *GetSpdyPreferredConn(nsConnectionEntry *ent); + nsDataHashtable<nsCStringHashKey, nsConnectionEntry *> mSpdyPreferredHash; + nsConnectionEntry *LookupConnectionEntry(nsHttpConnectionInfo *ci, + nsHttpConnection *conn, + nsHttpTransaction *trans); + + void ProcessSpdyPendingQ(nsConnectionEntry *ent); + void ProcessSpdyPendingQ(); + static PLDHashOperator ProcessSpdyPendingQCB( + const nsACString &key, nsAutoPtr<nsConnectionEntry> &ent, + void *closure); + // message handlers have this signature typedef void (nsHttpConnectionMgr:: *nsConnEventHandler)(PRInt32, void *); // nsConnEvent // // subclass of nsRunnable used to marshall events to the socket transport // thread. this class is used to implement PostEvent. // @@ -356,11 +408,18 @@ private: // // the connection table // // this table is indexed by connection key. each entry is a // nsConnectionEntry object. // nsClassHashtable<nsCStringHashKey, nsConnectionEntry> mCT; + + // this table is protected by the monitor + nsCStringHashSet mAlternateProtocolHash; + static PLDHashOperator TrimAlternateProtocolHash(PLDHashTable *table, + PLDHashEntryHdr *hdr, + PRUint32 number, + void *closure); }; #endif // !nsHttpConnectionMgr_h__
--- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -169,16 +169,17 @@ nsHttpHandler::nsHttpHandler() : mConnMgr(nsnull) , mHttpVersion(NS_HTTP_VERSION_1_1) , mProxyHttpVersion(NS_HTTP_VERSION_1_1) , mCapabilities(NS_HTTP_ALLOW_KEEPALIVE) , mProxyCapabilities(NS_HTTP_ALLOW_KEEPALIVE) , mReferrerLevel(0xff) // by default we always send a referrer , mFastFallbackToIPv4(false) , mIdleTimeout(10) + , mSpdyTimeout(180) , mMaxRequestAttempts(10) , mMaxRequestDelay(10) , mIdleSynTimeout(250) , mMaxConnections(24) , mMaxConnectionsPerServer(8) , mMaxPersistentConnectionsPerServer(2) , mMaxPersistentConnectionsPerProxy(4) , mMaxPipelinedRequests(2) @@ -193,16 +194,19 @@ nsHttpHandler::nsHttpHandler() , mLegacyAppVersion("5.0") , mProduct("Gecko") , mUserAgentIsDirty(true) , mUseCache(true) , mPromptTempRedirect(true) , mSendSecureXSiteReferrer(true) , mEnablePersistentHttpsCaching(false) , mDoNotTrackEnabled(false) + , mEnableSpdy(false) + , mCoalesceSpdy(true) + , mUseAlternateProtocol(false) { #if defined(PR_LOGGING) gHttpLog = PR_NewLogModule("nsHttp"); #endif LOG(("Creating nsHttpHandler [this=%x].\n", this)); NS_ASSERTION(!gHttpHandler, "HTTP handler already created!"); @@ -1079,16 +1083,41 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc } if (PREF_CHANGED(HTTP_PREF("phishy-userpass-length"))) { rv = prefs->GetIntPref(HTTP_PREF("phishy-userpass-length"), &val); if (NS_SUCCEEDED(rv)) mPhishyUserPassLength = (PRUint8) clamped(val, 0, 0xff); } + if (PREF_CHANGED(HTTP_PREF("spdy.enabled"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnableSpdy = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.coalesce-hostnames"), &cVar); + if (NS_SUCCEEDED(rv)) + mCoalesceSpdy = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.use-alternate-protocol"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.use-alternate-protocol"), + &cVar); + if (NS_SUCCEEDED(rv)) + mUseAlternateProtocol = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.timeout"), &val); + if (NS_SUCCEEDED(rv)) + mSpdyTimeout = (PRUint16) clamped(val, 1, 0xffff); + } + // // INTL options // if (PREF_CHANGED(INTL_ACCEPT_LANGUAGES)) { nsCOMPtr<nsIPrefLocalizedString> pls; prefs->GetComplexValue(INTL_ACCEPT_LANGUAGES, NS_GET_IID(nsIPrefLocalizedString),
--- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -96,27 +96,32 @@ public: const nsAFlatCString &UserAgent(); nsHttpVersion HttpVersion() { return mHttpVersion; } nsHttpVersion ProxyHttpVersion() { return mProxyHttpVersion; } PRUint8 ReferrerLevel() { return mReferrerLevel; } bool SendSecureXSiteReferrer() { return mSendSecureXSiteReferrer; } PRUint8 RedirectionLimit() { return mRedirectionLimit; } PRUint16 IdleTimeout() { return mIdleTimeout; } + PRUint16 SpdyTimeout() { return mSpdyTimeout; } PRUint16 MaxRequestAttempts() { return mMaxRequestAttempts; } const char *DefaultSocketType() { return mDefaultSocketType.get(); /* ok to return null */ } nsIIDNService *IDNConverter() { return mIDNConverter; } PRUint32 PhishyUserPassLength() { return mPhishyUserPassLength; } PRUint8 GetQoSBits() { return mQoSBits; } PRUint16 GetIdleSynTimeout() { return mIdleSynTimeout; } bool FastFallbackToIPv4() { return mFastFallbackToIPv4; } PRUint32 MaxSocketCount(); bool IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; } + bool IsSpdyEnabled() { return mEnableSpdy; } + bool CoalesceSpdy() { return mCoalesceSpdy; } + bool UseAlternateProtocol() { return mUseAlternateProtocol; } + bool PromptTempRedirect() { return mPromptTempRedirect; } nsHttpAuthCache *AuthCache() { return &mAuthCache; } nsHttpConnectionMgr *ConnMgr() { return mConnMgr; } // cache support nsresult GetCacheSession(nsCacheStoragePolicy, nsICacheSession **); PRUint32 GenerateUniqueID() { return ++mLastUniqueID; } @@ -259,16 +264,17 @@ private: PRUint8 mProxyHttpVersion; PRUint8 mCapabilities; PRUint8 mProxyCapabilities; PRUint8 mReferrerLevel; bool mFastFallbackToIPv4; PRUint16 mIdleTimeout; + PRUint16 mSpdyTimeout; PRUint16 mMaxRequestAttempts; PRUint16 mMaxRequestDelay; PRUint16 mIdleSynTimeout; PRUint16 mMaxConnections; PRUint8 mMaxConnectionsPerServer; PRUint8 mMaxPersistentConnectionsPerServer; PRUint8 mMaxPersistentConnectionsPerProxy; @@ -326,16 +332,21 @@ private: // if true allow referrer headers between secure non-matching hosts bool mSendSecureXSiteReferrer; // Persistent HTTPS caching flag bool mEnablePersistentHttpsCaching; // For broadcasting the preference to not be tracked bool mDoNotTrackEnabled; + + // Try to use SPDY features instead of HTTP/1.1 over SSL + bool mEnableSpdy; + bool mCoalesceSpdy; + bool mUseAlternateProtocol; }; //----------------------------------------------------------------------------- extern nsHttpHandler *gHttpHandler; //----------------------------------------------------------------------------- // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
--- a/netwerk/protocol/http/nsHttpPipeline.cpp +++ b/netwerk/protocol/http/nsHttpPipeline.cpp @@ -123,17 +123,17 @@ nsHttpPipeline::AddTransaction(nsAHttpTr NS_ADDREF(trans); mRequestQ.AppendElement(trans); if (mConnection) { trans->SetConnection(this); if (mRequestQ.Length() == 1) - mConnection->ResumeSend(); + mConnection->ResumeSend(trans); } return NS_OK; } //----------------------------------------------------------------------------- // nsHttpPipeline::nsISupports //----------------------------------------------------------------------------- @@ -162,29 +162,29 @@ nsHttpPipeline::OnHeadersAvailable(nsAHt NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(mConnection, "no connection"); // trans has now received its response headers; forward to the real connection return mConnection->OnHeadersAvailable(trans, requestHead, responseHead, reset); } nsresult -nsHttpPipeline::ResumeSend() +nsHttpPipeline::ResumeSend(nsAHttpTransaction *trans) { if (mConnection) - return mConnection->ResumeSend(); + return mConnection->ResumeSend(trans); return NS_ERROR_UNEXPECTED; } nsresult -nsHttpPipeline::ResumeRecv() +nsHttpPipeline::ResumeRecv(nsAHttpTransaction *trans) { NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(mConnection, "no connection"); - return mConnection->ResumeRecv(); + return mConnection->ResumeRecv(trans); } void nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) { LOG(("nsHttpPipeline::CloseTransaction [this=%x trans=%x reason=%x]\n", this, trans, reason)); @@ -369,16 +369,25 @@ nsHttpPipeline::SetConnection(nsAHttpCon NS_IF_ADDREF(mConnection = conn); PRInt32 i, count = mRequestQ.Length(); for (i=0; i<count; ++i) Request(i)->SetConnection(this); } +nsAHttpConnection * +nsHttpPipeline::Connection() +{ + LOG(("nsHttpPipeline::Connection [this=%x conn=%x]\n", this, mConnection)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + return mConnection; +} + void nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result, nsIEventTarget **target) { NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); // return security callbacks from first request nsAHttpTransaction *trans = Request(0);
--- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -300,16 +300,22 @@ nsHttpTransaction::Init(PRUint8 caps, nsIOService::gDefaultSegmentCount, nsIOService::gBufferCache); if (NS_FAILED(rv)) return rv; NS_ADDREF(*responseBody = mPipeIn); return NS_OK; } +nsAHttpConnection * +nsHttpTransaction::Connection() +{ + return mConnection; +} + nsHttpResponseHead * nsHttpTransaction::TakeResponseHead() { if (!mHaveAllHeaders) { NS_WARNING("response headers not available or incomplete"); return nsnull; } @@ -1282,30 +1288,30 @@ NS_IMPL_THREADSAFE_QUERY_INTERFACE2(nsHt // nsHttpTransaction::nsIInputStreamCallback //----------------------------------------------------------------------------- // called on the socket thread NS_IMETHODIMP nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out) { if (mConnection) { - nsresult rv = mConnection->ResumeSend(); + nsresult rv = mConnection->ResumeSend(this); if (NS_FAILED(rv)) NS_ERROR("ResumeSend failed"); } return NS_OK; } //----------------------------------------------------------------------------- // nsHttpTransaction::nsIOutputStreamCallback //----------------------------------------------------------------------------- // called on the socket thread NS_IMETHODIMP nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out) { if (mConnection) { - nsresult rv = mConnection->ResumeRecv(); + nsresult rv = mConnection->ResumeRecv(this); if (NS_FAILED(rv)) NS_ERROR("ResumeRecv failed"); } return NS_OK; }
--- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -115,28 +115,27 @@ public: // attributes PRUint8 Caps() { return mCaps; } nsHttpConnectionInfo *ConnectionInfo() { return mConnInfo; } nsHttpResponseHead *ResponseHead() { return mHaveAllHeaders ? mResponseHead : nsnull; } nsISupports *SecurityInfo() { return mSecurityInfo; } nsIInterfaceRequestor *Callbacks() { return mCallbacks; } nsIEventTarget *ConsumerTarget() { return mConsumerTarget; } - nsAHttpConnection *Connection() { return mConnection; } // Called to take ownership of the response headers; the transaction // will drop any reference to the response headers after this call. nsHttpResponseHead *TakeResponseHead(); // Called to find out if the transaction generated a complete response. bool ResponseIsComplete() { return mResponseIsComplete; } bool SSLConnectFailed() { return mSSLConnectFailed; } - // These methods may only be used by the connection manager. + // SetPriority() may only be used by the connection manager. void SetPriority(PRInt32 priority) { mPriority = priority; } PRInt32 Priority() { return mPriority; } const TimingStruct& Timings() const { return mTimings; } private: nsresult Restart(); char *LocateHttpStart(char *buf, PRUint32 len,
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -48,17 +48,17 @@ interface nsIAsyncInputStream; interface nsIAsyncOutputStream; interface nsIURI; interface nsIProxyInfo; /** * The callback interface for nsIHttpChannelInternal::HTTPUpgrade() */ -[scriptable, uuid(5644af88-09e1-4fbd-83da-f012b3b30180)] +[scriptable, uuid(4b967b6d-cd1c-49ae-a457-23ff76f5a2e8)] interface nsIHttpUpgradeListener : nsISupports { void onTransportAvailable(in nsISocketTransport aTransport, in nsIAsyncInputStream aSocketIn, in nsIAsyncOutputStream aSocketOut); }; /** @@ -176,9 +176,16 @@ interface nsIHttpChannelInternal : nsISu * @param aProtocolName * The value of the HTTP Upgrade request header * @param aListener * The callback object used to handle a successful upgrade */ void HTTPUpgrade(in ACString aProtocolName, in nsIHttpUpgradeListener aListener); + /** + * Enable/Disable Spdy negotiation on per channel basis. + * The network.http.spdy.enabled preference is still a pre-requisite + * for starting spdy. + */ + attribute boolean allowSpdy; + };
--- a/netwerk/socket/nsISSLSocketControl.idl +++ b/netwerk/socket/nsISSLSocketControl.idl @@ -37,16 +37,52 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" interface nsIInterfaceRequestor; -[scriptable, uuid(a092097c-8386-4f1b-97b1-90eb70008c2d)] +%{C++ +#include "nsTArray.h" +class nsCString; +%} +[ref] native nsCStringTArrayRef(nsTArray<nsCString>); + +[scriptable, uuid(753f0f13-681d-4de3-a6c6-11aa7e0b3afd)] interface nsISSLSocketControl : nsISupports { attribute nsIInterfaceRequestor notificationCallbacks; void proxyStartSSL(); void StartTLS(); + + /* NPN (Next Protocol Negotiation) is a mechanism for + negotiating the protocol to be spoken inside the SSL + tunnel during the SSL handshake. The NPNList is the list + of offered client side protocols. setNPNList() needs to + be called before any data is read or written (including the + handshake to be setup correctly. */ + + [noscript] void setNPNList(in nsCStringTArrayRef aNPNList); + + /* negotiatedNPN is '' if no NPN list was provided by the client, + * or if the server did not select any protocol choice from that + * list. That also includes the case where the server does not + * implement NPN. + * + * If negotiatedNPN is read before NPN has progressed to the point + * where this information is available NS_ERROR_NOT_CONNECTED is + * raised. + */ + readonly attribute ACString negotiatedNPN; + + /* Determine if a potential SSL connection to hostname:port with + * a desired NPN negotiated protocol of npnProtocol can use the socket + * associated with this object instead of making a new one. + */ + boolean joinConnection( + in ACString npnProtocol, /* e.g. "spdy/2" */ + in ACString hostname, + in long port); + };
--- a/netwerk/test/unit/test_URIs.js +++ b/netwerk/test/unit/test_URIs.js @@ -2,16 +2,19 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm"); const Ci = Components.interfaces; const Cr = Components.results; var gIoService = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); +// Run by: cd objdir; make -C netwerk/test/ xpcshell-tests +// or: cd objdir; make SOLO_FILE="test_URIs.js" -C netwerk/test/ check-one + // Relevant RFCs: 1738, 1808, 2396, 3986 (newer than the code) // http://greenbytes.de/tech/webdav/rfc3986.html#rfc.section.5.4 // http://greenbytes.de/tech/tc/uris/ // TEST DATA // --------- var gTests = [ { spec: "about:blank", @@ -111,16 +114,34 @@ var gTests = [ scheme: "ftp", prePath: "ftp://foo:bar@ftp.mozilla.org:100", port: 100, username: "foo", password: "bar", path: "/pub/mozilla.org/README", ref: "", nsIURL: true, nsINestedURI: false }, + { spec: "ftp://foo:@ftp.mozilla.org:100/pub/mozilla.org/README", + scheme: "ftp", + prePath: "ftp://foo:@ftp.mozilla.org:100", + port: 100, + username: "foo", + password: "", + path: "/pub/mozilla.org/README", + ref: "", + nsIURL: true, nsINestedURI: false }, + //Bug 706249 + { spec: "http:x:@", + scheme: "http", + prePath: "http://x:@", + username: "x", + password: "", + path: "", + ref: "", + nsIURL: true, nsINestedURI: false }, { spec: "gopher://mozilla.org/", scheme: "gopher", prePath: "gopher:", path: "//mozilla.org/", ref: "", nsIURL: false, nsINestedURI: false }, { spec: "http://", scheme: "http",
--- a/security/coreconf/coreconf.dep +++ b/security/coreconf/coreconf.dep @@ -37,8 +37,9 @@ /* * A dummy header file that is a dependency for all the object files. * Used to force a full recompilation of NSS in Mozilla's Tinderbox * depend builds. See comments in rules.mk. */ #error "Do not include this header file." +
--- a/security/manager/ssl/src/Makefile.in +++ b/security/manager/ssl/src/Makefile.in @@ -55,23 +55,23 @@ LIBXUL_LIBRARY = 1 CPPSRCS = \ nsCERTValInParamWrapper.cpp \ nsNSSCleaner.cpp \ nsCertOverrideService.cpp \ nsRecentBadCerts.cpp \ nsClientAuthRemember.cpp \ nsPSMBackgroundThread.cpp \ - nsSSLThread.cpp \ nsCertVerificationThread.cpp \ nsProtectedAuthThread.cpp \ nsNSSCallbacks.cpp \ nsNSSComponent.cpp \ nsNSSErrors.cpp \ nsNSSIOLayer.cpp \ + SSLServerCertVerification.cpp \ nsSSLStatus.cpp \ nsNSSModule.cpp \ nsSSLSocketProvider.cpp \ nsTLSSocketProvider.cpp \ nsSDR.cpp \ nsPK11TokenDB.cpp \ nsNSSCertificate.cpp \ nsPKCS12Blob.cpp \
copy from security/manager/ssl/src/nsNSSCallbacks.cpp copy to security/manager/ssl/src/SSLServerCertVerification.cpp --- a/security/manager/ssl/src/nsNSSCallbacks.cpp +++ b/security/manager/ssl/src/SSLServerCertVerification.cpp @@ -35,992 +35,254 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -#include "nsNSSComponent.h" // for PIPNSS string bundle calls. -#include "nsNSSCallbacks.h" + +/* + * All I/O is done on the socket transport thread, including all calls into + * libssl. That is, all SSL_* functions must be called on the socket transport + * thread. This also means that all SSL callback functions will be called on + * the socket transport thread, including in particular the auth certificate + * hook. + * + * During certificate authentication, we call CERT_PKIXVerifyCert or + * CERT_VerifyCert. These functions may make zero or more HTTP requests + * for OCSP responses, CRLs, intermediate certificates, etc. + * + * If our cert auth hook were to call the CERT_*Verify* functions directly, + * there would be a deadlock: The CERT_*Verify* function would cause an event + * to be asynchronously posted to the socket transport thread, and then it + * would block the socket transport thread waiting to be notified of the HTTP + * response. However, the HTTP request would never actually be processed + * because the socket transport thread would be blocked and so it wouldn't be + * able process HTTP requests. (i.e. Deadlock.) + * + * Consequently, we must always call the CERT_*Verify* cert functions off the + * socket transport thread. To accomplish this, our auth cert hook dispatches a + * SSLServerCertVerificationJob to a pool of background threads, and then + * immediatley return SECWouldBlock to libssl. These jobs are where the + * CERT_*Verify* functions are actually called. + * + * When our auth cert hook returns SECWouldBlock, libssl will carry on the + * handshake while we validate the certificate. This will free up the socket + * transport thread so that HTTP requests--in particular, the OCSP/CRL/cert + * requests needed for cert verification as mentioned above--can be processed. + * + * Once the CERT_*Verify* function returns, the cert verification job + * dispatches a SSLServerCertVerificationResult to the socket transport thread; + * the SSLServerCertVerificationResult will notify libssl that the certificate + * authentication is complete. Once libssl is notified that the authentication + * is complete, it will continue the SSL handshake (if it hasn't already + * finished) and it will begin allowing us to send/receive data on the + * connection. + * + * Timeline of events: + * + * * libssl calls SSLServerCertVerificationJob::Dispatch on the socket + * transport thread. + * * SSLServerCertVerificationJob::Dispatch queues a job + * (instance of SSLServerCertVerificationJob) to its background thread + * pool and returns. + * * One of the background threads calls CERT_*Verify*, which may enqueue + * some HTTP request(s) onto the socket transport thread, and then + * blocks that background thread waiting for the responses and/or timeouts + * or errors for those requests. + * * Once those HTTP responses have all come back or failed, the + * CERT_*Verify* function returns a result indicating that the validation + * succeeded or failed. + * * If the validation succeeded, then a SSLServerCertVerificationResult + * event is posted to the socket transport thread, and the cert + * verification thread becomes free to verify other certificates. + * * Otherwise, a CertErrorRunnable is posted to the socket transport thread + * and then to the main thread (blocking both, see CertErrorRunnable) to + * do cert override processing and bad cert listener notification. Then + * the cert verification thread becomes free to verify other certificates. + * * After processing cert overrides, the CertErrorRunnable will dispatch a + * SSLServerCertVerificationResult event to the socket transport thread to + * notify it of the result of the override processing; then it returns, + * freeing up the main thread. + * * The SSLServerCertVerificationResult event will either wake up the + * socket (using SSL_RestartHandshakeAfterServerCert) if validation + * succeeded or there was an error override, or it will set an error flag + * so that the next I/O operation on the socket will fail, causing the + * socket transport thread to close the connection. + * + * Cert override processing must happen on the main thread because it accesses + * the nsICertOverrideService, and that service must be accessed on the main + * thread because some extensions (Selenium, in particular) replace it with a + * Javascript implementation, and chrome JS must always be run on the main + * thread. + * + * SSLServerCertVerificationResult must be dispatched to the socket transport + * thread because we must only call SSL_* functions on the socket transport + * thread since they may do I/O, because many parts of nsNSSSocketInfo and + * the PSM NSS I/O layer are not thread-safe, and because we need the event to + * interrupt the PR_Poll that may waiting for I/O on the socket for which we + * are validating the cert. + */ + +#include "SSLServerCertVerification.h" +#include "nsNSSComponent.h" #include "nsNSSCertificate.h" -#include "nsNSSCleaner.h" -#include "nsSSLStatus.h" -#include "nsNSSIOLayer.h" // for nsNSSSocketInfo -#include "nsIWebProgressListener.h" -#include "nsIStringBundle.h" -#include "nsXPIDLString.h" -#include "nsCOMPtr.h" -#include "nsAutoPtr.h" -#include "nsIServiceManager.h" -#include "nsReadableUtils.h" -#include "nsIPrompt.h" -#include "nsIInterfaceRequestor.h" -#include "nsIInterfaceRequestorUtils.h" -#include "nsProtectedAuthThread.h" -#include "nsITokenDialogs.h" -#include "nsCRT.h" -#include "nsNSSShutDown.h" -#include "nsIUploadChannel.h" -#include "nsSSLThread.h" -#include "nsThreadUtils.h" -#include "nsIThread.h" -#include "nsIWindowWatcher.h" -#include "nsIPrompt.h" -#include "nsProxyRelease.h" -#include "PSMRunnable.h" -#include "nsIConsoleService.h" +#include "nsNSSIOLayer.h" + +#include "nsIThreadPool.h" +#include "nsXPCOMCIDInternal.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" #include "ssl.h" -#include "cert.h" -#include "ocsp.h" -#include "nssb64.h" #include "secerr.h" #include "sslerr.h" -using namespace mozilla; -using namespace mozilla::psm; - -static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); -NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate) - #ifdef PR_LOGGING extern PRLogModuleInfo* gPIPNSSLog; #endif -class nsHTTPDownloadEvent : public nsRunnable { -public: - nsHTTPDownloadEvent(); - ~nsHTTPDownloadEvent(); - - NS_IMETHOD Run(); - - nsNSSHttpRequestSession *mRequestSession; - - nsCOMPtr<nsHTTPListener> mListener; - bool mResponsibleForDoneSignal; -}; +namespace mozilla { namespace psm { -nsHTTPDownloadEvent::nsHTTPDownloadEvent() -:mResponsibleForDoneSignal(true) -{ -} - -nsHTTPDownloadEvent::~nsHTTPDownloadEvent() -{ - if (mResponsibleForDoneSignal && mListener) - mListener->send_done_signal(); - - mRequestSession->Release(); -} - -NS_IMETHODIMP -nsHTTPDownloadEvent::Run() -{ - if (!mListener) - return NS_OK; +namespace { +// do not use a nsCOMPtr to avoid static initializer/destructor +nsIThreadPool * gCertVerificationThreadPool = nsnull; +} // unnamed namespace - nsresult rv; - - nsCOMPtr<nsIIOService> ios = do_GetIOService(); - NS_ENSURE_STATE(ios); - - nsCOMPtr<nsIChannel> chan; - ios->NewChannel(mRequestSession->mURL, nsnull, nsnull, getter_AddRefs(chan)); - NS_ENSURE_STATE(chan); - - chan->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS); - - // Create a loadgroup for this new channel. This way if the channel - // is redirected, we'll have a way to cancel the resulting channel. - nsCOMPtr<nsILoadGroup> lg = do_CreateInstance(NS_LOADGROUP_CONTRACTID); - chan->SetLoadGroup(lg); - - if (mRequestSession->mHasPostData) - { - nsCOMPtr<nsIInputStream> uploadStream; - rv = NS_NewPostDataStream(getter_AddRefs(uploadStream), - false, - mRequestSession->mPostData, - 0, ios); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(chan)); - NS_ENSURE_STATE(uploadChannel); - - rv = uploadChannel->SetUploadStream(uploadStream, - mRequestSession->mPostContentType, - -1); - NS_ENSURE_SUCCESS(rv, rv); +// Called when the socket transport thread starts, to initialize the SSL cert +// verification thread pool. By tying the thread pool startup/shutdown directly +// to the STS thread's lifetime, we ensure that they are *always* available for +// SSL connections and that there are no races during startup and especially +// shutdown. (Previously, we have had multiple problems with races in PSM +// background threads, and the race-prevention/shutdown logic used there is +// brittle. Since this service is critical to things like downloading updates, +// we take no chances.) Also, by doing things this way, we avoid the need for +// locks, since gCertVerificationThreadPool is only ever accessed on the socket +// transport thread. +void +InitializeSSLServerCertVerificationThreads() +{ + // TODO: tuning, make parameters preferences + // XXX: instantiate nsThreadPool directly, to make this more bulletproof. + // Currently, the nsThreadPool.h header isn't exported for us to do so. + nsresult rv = CallCreateInstance(NS_THREADPOOL_CONTRACTID, + &gCertVerificationThreadPool); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create SSL cert verification threads."); + return; } - nsCOMPtr<nsIHttpChannel> hchan = do_QueryInterface(chan); - NS_ENSURE_STATE(hchan); - - rv = hchan->SetRequestMethod(mRequestSession->mRequestMethod); - NS_ENSURE_SUCCESS(rv, rv); - - mResponsibleForDoneSignal = false; - mListener->mResponsibleForDoneSignal = true; - - mListener->mLoadGroup = lg.get(); - NS_ADDREF(mListener->mLoadGroup); - mListener->mLoadGroupOwnerThread = PR_GetCurrentThread(); - - rv = NS_NewStreamLoader(getter_AddRefs(mListener->mLoader), - mListener); - - if (NS_SUCCEEDED(rv)) - rv = hchan->AsyncOpen(mListener->mLoader, nsnull); - - if (NS_FAILED(rv)) { - mListener->mResponsibleForDoneSignal = false; - mResponsibleForDoneSignal = true; - - NS_RELEASE(mListener->mLoadGroup); - mListener->mLoadGroup = nsnull; - mListener->mLoadGroupOwnerThread = nsnull; - } - - return NS_OK; -} - -struct nsCancelHTTPDownloadEvent : nsRunnable { - nsCOMPtr<nsHTTPListener> mListener; - - NS_IMETHOD Run() { - mListener->FreeLoadGroup(true); - mListener = nsnull; - return NS_OK; - } -}; - -SECStatus nsNSSHttpServerSession::createSessionFcn(const char *host, - PRUint16 portnum, - SEC_HTTP_SERVER_SESSION *pSession) -{ - if (!host || !pSession) - return SECFailure; - - nsNSSHttpServerSession *hss = new nsNSSHttpServerSession; - if (!hss) - return SECFailure; - - hss->mHost = host; - hss->mPort = portnum; - - *pSession = hss; - return SECSuccess; + (void) gCertVerificationThreadPool->SetIdleThreadLimit(5); + (void) gCertVerificationThreadPool->SetIdleThreadTimeout(30 * 1000); + (void) gCertVerificationThreadPool->SetThreadLimit(5); } -SECStatus nsNSSHttpRequestSession::createFcn(SEC_HTTP_SERVER_SESSION session, - const char *http_protocol_variant, - const char *path_and_query_string, - const char *http_request_method, - const PRIntervalTime timeout, - SEC_HTTP_REQUEST_SESSION *pRequest) -{ - if (!session || !http_protocol_variant || !path_and_query_string || - !http_request_method || !pRequest) - return SECFailure; - - nsNSSHttpServerSession* hss = static_cast<nsNSSHttpServerSession*>(session); - if (!hss) - return SECFailure; - - nsNSSHttpRequestSession *rs = new nsNSSHttpRequestSession; - if (!rs) - return SECFailure; - - rs->mTimeoutInterval = timeout; - - // Use a maximum timeout value of 10 seconds because of bug 404059. - // FIXME: Use a better approach once 406120 is ready. - PRUint32 maxBug404059Timeout = PR_TicksPerSecond() * 10; - if (timeout > maxBug404059Timeout) { - rs->mTimeoutInterval = maxBug404059Timeout; - } - - rs->mURL.Assign(http_protocol_variant); - rs->mURL.AppendLiteral("://"); - rs->mURL.Append(hss->mHost); - rs->mURL.AppendLiteral(":"); - rs->mURL.AppendInt(hss->mPort); - rs->mURL.Append(path_and_query_string); - - rs->mRequestMethod = http_request_method; - - *pRequest = (void*)rs; - return SECSuccess; -} - -SECStatus nsNSSHttpRequestSession::setPostDataFcn(const char *http_data, - const PRUint32 http_data_len, - const char *http_content_type) -{ - mHasPostData = true; - mPostData.Assign(http_data, http_data_len); - mPostContentType.Assign(http_content_type); - - return SECSuccess; -} - -SECStatus nsNSSHttpRequestSession::addHeaderFcn(const char *http_header_name, - const char *http_header_value) +// Called when the socket transport thread finishes, to destroy the thread +// pool. Since the socket transport service has stopped processing events, it +// will not attempt any more SSL I/O operations, so it is clearly safe to shut +// down the SSL cert verification infrastructure. Also, the STS will not +// dispatch many SSL verification result events at this point, so any pending +// cert verifications will (correctly) fail at the point they are dispatched. +// +// The other shutdown race condition that is possible is a race condition with +// shutdown of the nsNSSComponent service. We use the +// nsNSSShutdownPreventionLock where needed (not here) to prevent that. +void StopSSLServerCertVerificationThreads() { - return SECFailure; // not yet implemented - - // All http code needs to be postponed to the UI thread. - // Once this gets implemented, we need to add a string list member to - // nsNSSHttpRequestSession and queue up the headers, - // so they can be added in HandleHTTPDownloadPLEvent. - // - // The header will need to be set using - // mHttpChannel->SetRequestHeader(nsDependentCString(http_header_name), - // nsDependentCString(http_header_value), - // false))); -} - -SECStatus nsNSSHttpRequestSession::trySendAndReceiveFcn(PRPollDesc **pPollDesc, - PRUint16 *http_response_code, - const char **http_response_content_type, - const char **http_response_headers, - const char **http_response_data, - PRUint32 *http_response_data_len) -{ - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("nsNSSHttpRequestSession::trySendAndReceiveFcn to %s\n", mURL.get())); - - const int max_retries = 2; - int retry_count = 0; - bool retryable_error = false; - SECStatus result_sec_status = SECFailure; - - do - { - if (retry_count > 0) - { - if (retryable_error) - { - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("nsNSSHttpRequestSession::trySendAndReceiveFcn - sleeping and retrying: %d of %d\n", - retry_count, max_retries)); - } - - PR_Sleep( PR_MillisecondsToInterval(300) * retry_count ); - } - - ++retry_count; - retryable_error = false; - - result_sec_status = - internal_send_receive_attempt(retryable_error, pPollDesc, http_response_code, - http_response_content_type, http_response_headers, - http_response_data, http_response_data_len); - } - while (retryable_error && - retry_count < max_retries); - -#ifdef PR_LOGGING - if (retry_count > 1) - { - if (retryable_error) - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("nsNSSHttpRequestSession::trySendAndReceiveFcn - still failing, giving up...\n")); - else - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("nsNSSHttpRequestSession::trySendAndReceiveFcn - success at attempt %d\n", - retry_count)); - } -#endif - - return result_sec_status; -} - -void -nsNSSHttpRequestSession::AddRef() -{ - NS_AtomicIncrementRefcnt(mRefCount); -} - -void -nsNSSHttpRequestSession::Release() -{ - PRInt32 newRefCount = NS_AtomicDecrementRefcnt(mRefCount); - if (!newRefCount) { - delete this; + if (gCertVerificationThreadPool) { + gCertVerificationThreadPool->Shutdown(); + NS_RELEASE(gCertVerificationThreadPool); } } -SECStatus -nsNSSHttpRequestSession::internal_send_receive_attempt(bool &retryable_error, - PRPollDesc **pPollDesc, - PRUint16 *http_response_code, - const char **http_response_content_type, - const char **http_response_headers, - const char **http_response_data, - PRUint32 *http_response_data_len) -{ - if (pPollDesc) *pPollDesc = nsnull; - if (http_response_code) *http_response_code = 0; - if (http_response_content_type) *http_response_content_type = 0; - if (http_response_headers) *http_response_headers = 0; - if (http_response_data) *http_response_data = 0; - - PRUint32 acceptableResultSize = 0; - - if (http_response_data_len) - { - acceptableResultSize = *http_response_data_len; - *http_response_data_len = 0; - } - - if (!mListener) - return SECFailure; - - Mutex& waitLock = mListener->mLock; - CondVar& waitCondition = mListener->mCondition; - volatile bool &waitFlag = mListener->mWaitFlag; - waitFlag = true; - - nsRefPtr<nsHTTPDownloadEvent> event = new nsHTTPDownloadEvent; - if (!event) - return SECFailure; - - event->mListener = mListener; - this->AddRef(); - event->mRequestSession = this; +namespace { - nsresult rv = NS_DispatchToMainThread(event); - if (NS_FAILED(rv)) - { - event->mResponsibleForDoneSignal = false; - return SECFailure; - } - - bool request_canceled = false; - - { - MutexAutoLock locker(waitLock); - - const PRIntervalTime start_time = PR_IntervalNow(); - PRIntervalTime wait_interval; - - bool running_on_main_thread = NS_IsMainThread(); - if (running_on_main_thread) - { - // let's process events quickly - wait_interval = PR_MicrosecondsToInterval(50); - } - else - { - // On a secondary thread, it's fine to wait some more for - // for the condition variable. - wait_interval = PR_MillisecondsToInterval(250); - } - - while (waitFlag) - { - if (running_on_main_thread) - { - // Networking runs on the main thread, which we happen to block here. - // Processing events will allow the OCSP networking to run while we - // are waiting. Thanks a lot to Darin Fisher for rewriting the - // thread manager. Thanks a lot to Christian Biesinger who - // made me aware of this possibility. (kaie) - - MutexAutoUnlock unlock(waitLock); - NS_ProcessNextEvent(nsnull); - } - - waitCondition.Wait(wait_interval); - - if (!waitFlag) - break; +class SSLServerCertVerificationJob : public nsRunnable +{ +public: + // Must be called only on the socket transport thread + static SECStatus Dispatch(const void * fdForLogging, + nsNSSSocketInfo * infoObject, + CERTCertificate * serverCert); +private: + NS_DECL_NSIRUNNABLE - if (!request_canceled) - { - bool wantExit = nsSSLThread::stoppedOrStopping(); - bool timeout = - (PRIntervalTime)(PR_IntervalNow() - start_time) > mTimeoutInterval; - - if (wantExit || timeout) - { - request_canceled = true; + // Must be called only on the socket transport thread + SSLServerCertVerificationJob(const void * fdForLogging, + nsNSSSocketInfo & socketInfo, + CERTCertificate & cert); + ~SSLServerCertVerificationJob(); - nsRefPtr<nsCancelHTTPDownloadEvent> cancelevent = new nsCancelHTTPDownloadEvent; - cancelevent->mListener = mListener; - rv = NS_DispatchToMainThread(cancelevent); - if (NS_FAILED(rv)) { - NS_WARNING("cannot post cancel event"); - } - break; - } - } - } - } - - if (request_canceled) - return SECFailure; - - if (NS_FAILED(mListener->mResultCode)) - { - if (mListener->mResultCode == NS_ERROR_CONNECTION_REFUSED - || - mListener->mResultCode == NS_ERROR_NET_RESET) - { - retryable_error = true; - } - return SECFailure; - } - - if (http_response_code) - *http_response_code = mListener->mHttpResponseCode; - - if (mListener->mHttpRequestSucceeded && http_response_data && http_response_data_len) { + // Runs on one of the background threads + SECStatus AuthCertificate(const nsNSSShutDownPreventionLock & proofOfLock); - *http_response_data_len = mListener->mResultLen; - - // acceptableResultSize == 0 means: any size is acceptable - if (acceptableResultSize != 0 - && - acceptableResultSize < mListener->mResultLen) - { - return SECFailure; - } - - // return data by reference, result data will be valid - // until "this" gets destroyed by NSS - *http_response_data = (const char*)mListener->mResultData; - } - - if (mListener->mHttpRequestSucceeded && http_response_content_type) { - if (mListener->mHttpResponseContentType.Length()) { - *http_response_content_type = mListener->mHttpResponseContentType.get(); - } - } - - return SECSuccess; -} + const void * const mFdForLogging; + const nsRefPtr<nsNSSSocketInfo> mSocketInfo; + CERTCertificate * const mCert; +}; -SECStatus nsNSSHttpRequestSession::cancelFcn() -{ - // As of today, only the blocking variant of the http interface - // has been implemented. Implementing cancelFcn will be necessary - // as soon as we implement the nonblocking variant. - return SECSuccess; -} - -SECStatus nsNSSHttpRequestSession::freeFcn() -{ - Release(); - return SECSuccess; -} - -nsNSSHttpRequestSession::nsNSSHttpRequestSession() -: mRefCount(1), - mHasPostData(false), - mTimeoutInterval(0), - mListener(new nsHTTPListener) -{ -} - -nsNSSHttpRequestSession::~nsNSSHttpRequestSession() +SSLServerCertVerificationJob::SSLServerCertVerificationJob( + const void * fdForLogging, nsNSSSocketInfo & socketInfo, + CERTCertificate & cert) + : mFdForLogging(fdForLogging) + , mSocketInfo(&socketInfo) + , mCert(CERT_DupCertificate(&cert)) { } -SEC_HttpClientFcn nsNSSHttpInterface::sNSSInterfaceTable; - -void nsNSSHttpInterface::initTable() -{ - sNSSInterfaceTable.version = 1; - SEC_HttpClientFcnV1 &v1 = sNSSInterfaceTable.fcnTable.ftable1; - v1.createSessionFcn = createSessionFcn; - v1.keepAliveSessionFcn = keepAliveFcn; - v1.freeSessionFcn = freeSessionFcn; - v1.createFcn = createFcn; - v1.setPostDataFcn = setPostDataFcn; - v1.addHeaderFcn = addHeaderFcn; - v1.trySendAndReceiveFcn = trySendAndReceiveFcn; - v1.cancelFcn = cancelFcn; - v1.freeFcn = freeFcn; -} - -void nsNSSHttpInterface::registerHttpClient() -{ - SEC_RegisterDefaultHttpClient(&sNSSInterfaceTable); -} - -void nsNSSHttpInterface::unregisterHttpClient() -{ - SEC_RegisterDefaultHttpClient(nsnull); -} - -nsHTTPListener::nsHTTPListener() -: mResultData(nsnull), - mResultLen(0), - mLock("nsHTTPListener.mLock"), - mCondition(mLock, "nsHTTPListener.mCondition"), - mWaitFlag(true), - mResponsibleForDoneSignal(false), - mLoadGroup(nsnull), - mLoadGroupOwnerThread(nsnull) -{ -} - -nsHTTPListener::~nsHTTPListener() -{ - if (mResponsibleForDoneSignal) - send_done_signal(); - - if (mLoader) { - nsCOMPtr<nsIThread> mainThread(do_GetMainThread()); - NS_ProxyRelease(mainThread, mLoader); - } -} - -NS_IMPL_THREADSAFE_ISUPPORTS1(nsHTTPListener, nsIStreamLoaderObserver) - -void -nsHTTPListener::FreeLoadGroup(bool aCancelLoad) -{ - nsILoadGroup *lg = nsnull; - - MutexAutoLock locker(mLock); - - if (mLoadGroup) { - if (mLoadGroupOwnerThread != PR_GetCurrentThread()) { - NS_ASSERTION(false, - "attempt to access nsHTTPDownloadEvent::mLoadGroup on multiple threads, leaking it!"); - } - else { - lg = mLoadGroup; - mLoadGroup = nsnull; - } - } - - if (lg) { - if (aCancelLoad) { - lg->Cancel(NS_ERROR_ABORT); - } - NS_RELEASE(lg); - } -} - -NS_IMETHODIMP -nsHTTPListener::OnStreamComplete(nsIStreamLoader* aLoader, - nsISupports* aContext, - nsresult aStatus, - PRUint32 stringLen, - const PRUint8* string) +SSLServerCertVerificationJob::~SSLServerCertVerificationJob() { - mResultCode = aStatus; - - FreeLoadGroup(false); - - nsCOMPtr<nsIRequest> req; - nsCOMPtr<nsIHttpChannel> hchan; - - nsresult rv = aLoader->GetRequest(getter_AddRefs(req)); - -#ifdef PR_LOGGING - if (NS_FAILED(aStatus)) - { - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("nsHTTPListener::OnStreamComplete status failed %d", aStatus)); - } -#endif - - if (NS_SUCCEEDED(rv)) - hchan = do_QueryInterface(req, &rv); - - if (NS_SUCCEEDED(rv)) - { - rv = hchan->GetRequestSucceeded(&mHttpRequestSucceeded); - if (NS_FAILED(rv)) - mHttpRequestSucceeded = false; - - mResultLen = stringLen; - mResultData = string; // reference. Make sure loader lives as long as this - - unsigned int rcode; - rv = hchan->GetResponseStatus(&rcode); - if (NS_FAILED(rv)) - mHttpResponseCode = 500; - else - mHttpResponseCode = rcode; - - hchan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"), - mHttpResponseContentType); - } - - if (mResponsibleForDoneSignal) - send_done_signal(); - - return aStatus; -} - -void nsHTTPListener::send_done_signal() -{ - mResponsibleForDoneSignal = false; - - { - MutexAutoLock locker(mLock); - mWaitFlag = false; - mCondition.NotifyAll(); - } -} - -static char* -ShowProtectedAuthPrompt(PK11SlotInfo* slot, nsIInterfaceRequestor *ir) -{ - if (!NS_IsMainThread()) { - NS_ERROR("ShowProtectedAuthPrompt called off the main thread"); - return nsnull; - } - - char* protAuthRetVal = nsnull; - - // Get protected auth dialogs - nsITokenDialogs* dialogs = 0; - nsresult nsrv = getNSSDialogs((void**)&dialogs, - NS_GET_IID(nsITokenDialogs), - NS_TOKENDIALOGS_CONTRACTID); - if (NS_SUCCEEDED(nsrv)) - { - nsProtectedAuthThread* protectedAuthRunnable = new nsProtectedAuthThread(); - if (protectedAuthRunnable) - { - NS_ADDREF(protectedAuthRunnable); - - protectedAuthRunnable->SetParams(slot); - - nsCOMPtr<nsIProtectedAuthThread> runnable = do_QueryInterface(protectedAuthRunnable); - if (runnable) - { - nsrv = dialogs->DisplayProtectedAuth(ir, runnable); - - // We call join on the thread, - // so we can be sure that no simultaneous access will happen. - protectedAuthRunnable->Join(); - - if (NS_SUCCEEDED(nsrv)) - { - SECStatus rv = protectedAuthRunnable->GetResult(); - switch (rv) - { - case SECSuccess: - protAuthRetVal = ToNewCString(nsDependentCString(PK11_PW_AUTHENTICATED)); - break; - case SECWouldBlock: - protAuthRetVal = ToNewCString(nsDependentCString(PK11_PW_RETRY)); - break; - default: - protAuthRetVal = nsnull; - break; - - } - } - } - - NS_RELEASE(protectedAuthRunnable); - } - - NS_RELEASE(dialogs); - } - - return protAuthRetVal; + CERT_DestroyCertificate(mCert); } -class PK11PasswordPromptRunnable : public SyncRunnableBase -{ -public: - PK11PasswordPromptRunnable(PK11SlotInfo* slot, - nsIInterfaceRequestor* ir) - : mResult(nsnull), - mSlot(slot), - mIR(ir) - { - } - char * mResult; // out - virtual void RunOnTargetThread(); -private: - PK11SlotInfo* const mSlot; // in - nsIInterfaceRequestor* const mIR; // in -}; - -void PK11PasswordPromptRunnable::RunOnTargetThread() -{ - nsNSSShutDownPreventionLock locker; - nsresult rv = NS_OK; - PRUnichar *password = nsnull; - bool value = false; - nsCOMPtr<nsIPrompt> prompt; - - /* TODO: Retry should generate a different dialog message */ -/* - if (retry) - return nsnull; -*/ - - if (!mIR) - { - nsNSSComponent::GetNewPrompter(getter_AddRefs(prompt)); - } - else - { - prompt = do_GetInterface(mIR); - NS_ASSERTION(prompt != nsnull, "callbacks does not implement nsIPrompt"); - } - - if (!prompt) - return; - - if (PK11_ProtectedAuthenticationPath(mSlot)) { - mResult = ShowProtectedAuthPrompt(mSlot, mIR); - return; - } - - nsAutoString promptString; - nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv)); - - if (NS_FAILED(rv)) - return; - - const PRUnichar* formatStrings[1] = { - ToNewUnicode(NS_ConvertUTF8toUTF16(PK11_GetTokenName(mSlot))) - }; - rv = nssComponent->PIPBundleFormatStringFromName("CertPassPrompt", - formatStrings, 1, - promptString); - nsMemory::Free(const_cast<PRUnichar*>(formatStrings[0])); - - if (NS_FAILED(rv)) - return; - - { - nsPSMUITracker tracker; - if (tracker.isUIForbidden()) { - rv = NS_ERROR_NOT_AVAILABLE; - } - else { - // Although the exact value is ignored, we must not pass invalid - // bool values through XPConnect. - bool checkState = false; - rv = prompt->PromptPassword(nsnull, promptString.get(), - &password, nsnull, &checkState, &value); - } - } - - if (NS_SUCCEEDED(rv) && value) { - mResult = ToNewUTF8String(nsDependentString(password)); - NS_Free(password); - } -} - -char* PR_CALLBACK -PK11PasswordPrompt(PK11SlotInfo* slot, PRBool retry, void* arg) -{ - nsRefPtr<PK11PasswordPromptRunnable> runnable = - new PK11PasswordPromptRunnable(slot, - static_cast<nsIInterfaceRequestor*>(arg)); - if (NS_IsMainThread()) { - runnable->RunOnTargetThread(); - } else { - runnable->DispatchToMainThreadAndWait(); - } - return runnable->mResult; -} - -void PR_CALLBACK HandshakeCallback(PRFileDesc* fd, void* client_data) { - nsNSSShutDownPreventionLock locker; - PRInt32 sslStatus; - char* signer = nsnull; - char* cipherName = nsnull; - PRInt32 keyLength; - nsresult rv; - PRInt32 encryptBits; - - if (SECSuccess != SSL_SecurityStatus(fd, &sslStatus, &cipherName, &keyLength, - &encryptBits, &signer, nsnull)) { - return; - } - - PRInt32 secStatus; - if (sslStatus == SSL_SECURITY_STATUS_OFF) - secStatus = nsIWebProgressListener::STATE_IS_BROKEN; - else if (encryptBits >= 90) - secStatus = (nsIWebProgressListener::STATE_IS_SECURE | - nsIWebProgressListener::STATE_SECURE_HIGH); - else - secStatus = (nsIWebProgressListener::STATE_IS_SECURE | - nsIWebProgressListener::STATE_SECURE_LOW); - - PRBool siteSupportsSafeRenego; - if (SSL_HandshakeNegotiatedExtension(fd, ssl_renegotiation_info_xtn, &siteSupportsSafeRenego) != SECSuccess - || !siteSupportsSafeRenego) { - - bool wantWarning = (nsSSLIOLayerHelpers::getWarnLevelMissingRFC5746() > 0); - - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; - nsCOMPtr<nsIConsoleService> console; - if (infoObject && wantWarning) { - console = do_GetService(NS_CONSOLESERVICE_CONTRACTID); - if (console) { - nsXPIDLCString hostName; - infoObject->GetHostName(getter_Copies(hostName)); - - nsAutoString msg; - msg.Append(NS_ConvertASCIItoUTF16(hostName)); - msg.Append(NS_LITERAL_STRING(" : server does not support RFC 5746, see CVE-2009-3555")); - - console->LogStringMessage(msg.get()); - } - } - if (nsSSLIOLayerHelpers::treatUnsafeNegotiationAsBroken()) { - secStatus = nsIWebProgressListener::STATE_IS_BROKEN; - } - } - - - CERTCertificate *peerCert = SSL_PeerCertificate(fd); - const char* caName = nsnull; // caName is a pointer only, no ownership - char* certOrgName = CERT_GetOrgName(&peerCert->issuer); - CERT_DestroyCertificate(peerCert); - caName = certOrgName ? certOrgName : signer; - - const char* verisignName = "Verisign, Inc."; - // If the CA name is RSA Data Security, then change the name to the real - // name of the company i.e. VeriSign, Inc. - if (nsCRT::strcmp((const char*)caName, "RSA Data Security, Inc.") == 0) { - caName = verisignName; - } - - nsAutoString shortDesc; - const PRUnichar* formatStrings[1] = { ToNewUnicode(NS_ConvertUTF8toUTF16(caName)) }; - nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv)); - if (NS_SUCCEEDED(rv)) { - rv = nssComponent->PIPBundleFormatStringFromName("SignedBy", - formatStrings, 1, - shortDesc); - - nsMemory::Free(const_cast<PRUnichar*>(formatStrings[0])); - - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; - infoObject->SetSecurityState(secStatus); - infoObject->SetShortSecurityDescription(shortDesc.get()); - - /* Set the SSL Status information */ - nsRefPtr<nsSSLStatus> status = infoObject->SSLStatus(); - if (!status) { - status = new nsSSLStatus(); - infoObject->SetSSLStatus(status); - } - - nsSSLIOLayerHelpers::mHostsWithCertErrors->LookupCertErrorBits( - infoObject, status); - - CERTCertificate *serverCert = SSL_PeerCertificate(fd); - if (serverCert) { - nsRefPtr<nsNSSCertificate> nssc = nsNSSCertificate::Create(serverCert); - CERT_DestroyCertificate(serverCert); - serverCert = nsnull; - - nsCOMPtr<nsIX509Cert> prevcert; - infoObject->GetPreviousCert(getter_AddRefs(prevcert)); - - bool equals_previous = false; - if (prevcert && nssc) { - nsresult rv = nssc->Equals(prevcert, &equals_previous); - if (NS_FAILED(rv)) { - equals_previous = false; - } - } - - if (equals_previous) { - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("HandshakeCallback using PREV cert %p\n", prevcert.get())); - status->mServerCert = prevcert; - } - else { - if (status->mServerCert) { - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("HandshakeCallback KEEPING cert %p\n", status->mServerCert.get())); - } - else { - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("HandshakeCallback using NEW cert %p\n", nssc.get())); - status->mServerCert = nssc; - } - } - } - - status->mHaveKeyLengthAndCipher = true; - status->mKeyLength = keyLength; - status->mSecretKeyLength = encryptBits; - status->mCipherName.Assign(cipherName); - } - - PORT_Free(cipherName); - PR_FREEIF(certOrgName); - PR_Free(signer); -} +static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); SECStatus -PSM_SSL_PKIX_AuthCertificate(PRFileDesc *fd, CERTCertificate *peerCert, bool checksig, bool isServer) +PSM_SSL_PKIX_AuthCertificate(CERTCertificate *peerCert, void * pinarg, + const char * hostname, + const nsNSSShutDownPreventionLock & /*proofOfLock*/) { SECStatus rv; - SECCertUsage certUsage; - SECCertificateUsage certificateusage; - void * pinarg; - char * hostname; - pinarg = SSL_RevealPinArg(fd); - hostname = SSL_RevealURL(fd); - - /* this may seem backwards, but isn't. */ - certUsage = isServer ? certUsageSSLClient : certUsageSSLServer; - certificateusage = isServer ? certificateUsageSSLClient : certificateUsageSSLServer; - if (!nsNSSComponent::globalConstFlagUsePKIXVerification) { - rv = CERT_VerifyCertNow(CERT_GetDefaultCertDB(), peerCert, checksig, certUsage, - pinarg); + rv = CERT_VerifyCertNow(CERT_GetDefaultCertDB(), peerCert, true, + certUsageSSLServer, pinarg); } else { nsresult nsrv; nsCOMPtr<nsINSSComponent> inss = do_GetService(kNSSComponentCID, &nsrv); if (!inss) return SECFailure; nsRefPtr<nsCERTValInParamWrapper> survivingParams; if (NS_FAILED(inss->GetDefaultCERTValInParam(survivingParams))) return SECFailure; CERTValOutParam cvout[1]; cvout[0].type = cert_po_end; - rv = CERT_PKIXVerifyCert(peerCert, certificateusage, + rv = CERT_PKIXVerifyCert(peerCert, certificateUsageSSLServer, survivingParams->GetRawPointerForNSS(), cvout, pinarg); } - if ( rv == SECSuccess && !isServer ) { + if (rv == SECSuccess) { /* cert is OK. This is the client side of an SSL connection. * Now check the name field in the cert against the desired hostname. * NB: This is our only defense against Man-In-The-Middle (MITM) attacks! */ if (hostname && hostname[0]) rv = CERT_VerifyCertName(peerCert, hostname); else rv = SECFailure; if (rv != SECSuccess) PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); } - PORT_Free(hostname); return rv; } struct nsSerialBinaryBlacklistEntry { unsigned int len; const char *binary_serial; }; @@ -1098,32 +360,98 @@ PSM_SSL_BlacklistDigiNotar(CERTCertifica // let's see if we want to worsen the error code to revoked. PRErrorCode revoked_code = PSM_SSL_DigiNotarTreatAsRevoked(serverCert, serverCertChain); return (revoked_code != 0) ? revoked_code : SEC_ERROR_UNTRUSTED_ISSUER; } return 0; } +// This function assumes that we will only use the SPDY connection coalescing +// feature on connections where we have negotiated SPDY using NPN. If we ever +// talk SPDY without having negotiated it with SPDY, this code will give wrong +// and perhaps unsafe results. +// +// Returns SECSuccess on the initial handshake of all connections, on +// renegotiations for any connections where we did not negotiate SPDY, or on any +// SPDY connection where the server's certificate did not change. +// +// Prohibit changing the server cert only if we negotiated SPDY, +// in order to support SPDY's cross-origin connection pooling. -SECStatus PR_CALLBACK AuthCertificateCallback(void* client_data, PRFileDesc* fd, - PRBool checksig, PRBool isServer) { - nsNSSShutDownPreventionLock locker; +static SECStatus +BlockServerCertChangeForSpdy(nsNSSSocketInfo *infoObject, + CERTCertificate *serverCert) +{ + // Get the existing cert. If there isn't one, then there is + // no cert change to worry about. + nsCOMPtr<nsIX509Cert> cert; + nsCOMPtr<nsIX509Cert2> cert2; + + nsRefPtr<nsSSLStatus> status = infoObject->SSLStatus(); + if (!status) { + // If we didn't have a status, then this is the + // first handshake on this connection, not a + // renegotiation. + return SECSuccess; + } + + status->GetServerCert(getter_AddRefs(cert)); + cert2 = do_QueryInterface(cert); + if (!cert2) { + NS_NOTREACHED("every nsSSLStatus must have a cert" + "that implements nsIX509Cert2"); + PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0); + return SECFailure; + } - CERTCertificate *serverCert = SSL_PeerCertificate(fd); - CERTCertificateCleaner serverCertCleaner(serverCert); + // Filter out sockets that did not neogtiate SPDY via NPN + nsCAutoString negotiatedNPN; + nsresult rv = infoObject->GetNegotiatedNPN(negotiatedNPN); + NS_ASSERTION(NS_SUCCEEDED(rv), + "GetNegotiatedNPN() failed during renegotiation"); + + if (NS_SUCCEEDED(rv) && !negotiatedNPN.Equals(NS_LITERAL_CSTRING("spdy/2"))) + return SECSuccess; + + // If GetNegotiatedNPN() failed we will assume spdy for safety's safe + if (NS_FAILED(rv)) + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("BlockServerCertChangeForSpdy failed GetNegotiatedNPN() call." + " Assuming spdy.\n")); - if (serverCert && - serverCert->serialNumber.data && - serverCert->issuerName && - !strcmp(serverCert->issuerName, + // Check to see if the cert has actually changed + CERTCertificate * c = cert2->GetCert(); + NS_ASSERTION(c, "very bad and hopefully impossible state"); + bool sameCert = CERT_CompareCerts(c, serverCert); + CERT_DestroyCertificate(c); + if (sameCert) + return SECSuccess; + + // Report an error - changed cert is confirmed + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("SPDY Refused to allow new cert during renegotiation\n")); + PR_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, 0); + return SECFailure; +} + +SECStatus +SSLServerCertVerificationJob::AuthCertificate( + nsNSSShutDownPreventionLock const & nssShutdownPreventionLock) +{ + if (BlockServerCertChangeForSpdy(mSocketInfo, mCert) != SECSuccess) + return SECFailure; + + if (mCert->serialNumber.data && + mCert->issuerName && + !strcmp(mCert->issuerName, "CN=UTN-USERFirst-Hardware,OU=http://www.usertrust.com,O=The USERTRUST Network,L=Salt Lake City,ST=UT,C=US")) { - unsigned char *server_cert_comparison_start = (unsigned char*)serverCert->serialNumber.data; - unsigned int server_cert_comparison_len = serverCert->serialNumber.len; + unsigned char *server_cert_comparison_start = mCert->serialNumber.data; + unsigned int server_cert_comparison_len = mCert->serialNumber.len; while (server_cert_comparison_len) { if (*server_cert_comparison_start != 0) break; ++server_cert_comparison_start; --server_cert_comparison_len; } @@ -1145,303 +473,281 @@ SECStatus PR_CALLBACK AuthCertificateCal if (server_cert_comparison_len == locked_cert_comparison_len && !memcmp(server_cert_comparison_start, locked_cert_comparison_start, locked_cert_comparison_len)) { PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0); return SECFailure; } } } - SECStatus rv = PSM_SSL_PKIX_AuthCertificate(fd, serverCert, checksig, isServer); + SECStatus rv = PSM_SSL_PKIX_AuthCertificate(mCert, mSocketInfo, + mSocketInfo->GetHostName(), + nssShutdownPreventionLock); // We want to remember the CA certs in the temp db, so that the application can find the // complete chain at any time it might need it. // But we keep only those CA certs in the temp db, that we didn't already know. - if (serverCert) { - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; - nsRefPtr<nsSSLStatus> status = infoObject->SSLStatus(); - nsRefPtr<nsNSSCertificate> nsc; + nsRefPtr<nsSSLStatus> status = mSocketInfo->SSLStatus(); + nsRefPtr<nsNSSCertificate> nsc; - if (!status || !status->mServerCert) { - nsc = nsNSSCertificate::Create(serverCert); - } + if (!status || !status->mServerCert) { + nsc = nsNSSCertificate::Create(mCert); + } - CERTCertList *certList = nsnull; - certList = CERT_GetCertChainFromCert(serverCert, PR_Now(), certUsageSSLCA); - if (!certList) { - rv = SECFailure; - } else { - PRErrorCode blacklistErrorCode; - if (rv == SECSuccess) { // PSM_SSL_PKIX_AuthCertificate said "valid cert" - blacklistErrorCode = PSM_SSL_BlacklistDigiNotar(serverCert, certList); - } else { // PSM_SSL_PKIX_AuthCertificate said "invalid cert" - PRErrorCode savedErrorCode = PORT_GetError(); - // Check if we want to worsen the error code to "revoked". - blacklistErrorCode = PSM_SSL_DigiNotarTreatAsRevoked(serverCert, certList); - if (blacklistErrorCode == 0) { - // we don't worsen the code, let's keep the original error code from NSS - PORT_SetError(savedErrorCode); - } - } - - if (blacklistErrorCode != 0) { - infoObject->SetCertIssuerBlacklisted(); - PORT_SetError(blacklistErrorCode); - rv = SECFailure; + CERTCertList *certList = nsnull; + certList = CERT_GetCertChainFromCert(mCert, PR_Now(), certUsageSSLCA); + if (!certList) { + rv = SECFailure; + } else { + PRErrorCode blacklistErrorCode; + if (rv == SECSuccess) { // PSM_SSL_PKIX_AuthCertificate said "valid cert" + blacklistErrorCode = PSM_SSL_BlacklistDigiNotar(mCert, certList); + } else { // PSM_SSL_PKIX_AuthCertificate said "invalid cert" + PRErrorCode savedErrorCode = PORT_GetError(); + // Check if we want to worsen the error code to "revoked". + blacklistErrorCode = PSM_SSL_DigiNotarTreatAsRevoked(mCert, certList); + if (blacklistErrorCode == 0) { + // we don't worsen the code, let's keep the original error code from NSS + PORT_SetError(savedErrorCode); } } - - if (rv == SECSuccess) { - if (nsc) { - bool dummyIsEV; - nsc->GetIsExtendedValidation(&dummyIsEV); // the nsc object will cache the status - } - - nsCOMPtr<nsINSSComponent> nssComponent; - for (CERTCertListNode *node = CERT_LIST_HEAD(certList); - !CERT_LIST_END(node, certList); - node = CERT_LIST_NEXT(node)) { - - if (node->cert->slot) { - // This cert was found on a token, no need to remember it in the temp db. - continue; - } + if (blacklistErrorCode != 0) { + mSocketInfo->SetCertIssuerBlacklisted(); + PORT_SetError(blacklistErrorCode); + rv = SECFailure; + } + } - if (node->cert->isperm) { - // We don't need to remember certs already stored in perm db. - continue; - } - - if (node->cert == serverCert) { - // We don't want to remember the server cert, - // the code that cares for displaying page info does this already. - continue; - } + if (rv == SECSuccess) { + if (nsc) { + bool dummyIsEV; + nsc->GetIsExtendedValidation(&dummyIsEV); // the nsc object will cache the status + } + + nsCOMPtr<nsINSSComponent> nssComponent; + + for (CERTCertListNode *node = CERT_LIST_HEAD(certList); + !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { - // We have found a signer cert that we want to remember. - char* nickname = nsNSSCertificate::defaultServerNickname(node->cert); - if (nickname && *nickname) { - PK11SlotInfo *slot = PK11_GetInternalKeySlot(); - if (slot) { - PK11_ImportCert(slot, node->cert, CK_INVALID_HANDLE, - nickname, false); - PK11_FreeSlot(slot); - } - } - PR_FREEIF(nickname); + if (node->cert->slot) { + // This cert was found on a token, no need to remember it in the temp db. + continue; } + if (node->cert->isperm) { + // We don't need to remember certs already stored in perm db. + continue; + } + + if (node->cert == mCert) { + // We don't want to remember the server cert, + // the code that cares for displaying page info does this already. + continue; + } + + // We have found a signer cert that we want to remember. + char* nickname = nsNSSCertificate::defaultServerNickname(node->cert); + if (nickname && *nickname) { + PK11SlotInfo *slot = PK11_GetInternalKeySlot(); + if (slot) { + PK11_ImportCert(slot, node->cert, CK_INVALID_HANDLE, + nickname, false); + PK11_FreeSlot(slot); + } + } + PR_FREEIF(nickname); } if (certList) { CERT_DestroyCertList(certList); } // The connection may get terminated, for example, if the server requires // a client cert. Let's provide a minimal SSLStatus // to the caller that contains at least the cert and its status. if (!status) { status = new nsSSLStatus(); - infoObject->SetSSLStatus(status); + mSocketInfo->SetSSLStatus(status); } if (rv == SECSuccess) { // Certificate verification succeeded delete any potential record // of certificate error bits. nsSSLIOLayerHelpers::mHostsWithCertErrors->RememberCertHasError( - infoObject, nsnull, rv); + mSocketInfo, nsnull, rv); } else { // Certificate verification failed, update the status' bits. nsSSLIOLayerHelpers::mHostsWithCertErrors->LookupCertErrorBits( - infoObject, status); + mSocketInfo, status); } if (status && !status->mServerCert) { status->mServerCert = nsc; PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("AuthCertificateCallback setting NEW cert %p\n", status->mServerCert.get())); + ("AuthCertificate setting NEW cert %p\n", status->mServerCert.get())); } } return rv; } -struct OCSPDefaultResponders { - const char *issuerName_string; - CERTName *issuerName; - const char *issuerKeyID_base64; - SECItem *issuerKeyID; - const char *ocspUrl; -}; +/*static*/ SECStatus +SSLServerCertVerificationJob::Dispatch(const void * fdForLogging, + nsNSSSocketInfo * socketInfo, + CERTCertificate * serverCert) +{ + // Runs on the socket transport thread + + if (!socketInfo || !serverCert) { + NS_ERROR("Invalid parameters for SSL server cert validation"); + socketInfo->SetCertVerificationResult(PR_INVALID_STATE_ERROR, + PlainErrorMessage); + PR_SetError(PR_INVALID_STATE_ERROR, 0); + return SECFailure; + } + + nsRefPtr<SSLServerCertVerificationJob> job + = new SSLServerCertVerificationJob(fdForLogging, *socketInfo, *serverCert); + + socketInfo->SetCertVerificationWaiting(); + nsresult nrv; + if (!gCertVerificationThreadPool) { + nrv = NS_ERROR_NOT_INITIALIZED; + } else { + nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL); + } + if (NS_FAILED(nrv)) { + PRErrorCode error = nrv == NS_ERROR_OUT_OF_MEMORY + ? SEC_ERROR_NO_MEMORY + : PR_INVALID_STATE_ERROR; + socketInfo->SetCertVerificationResult(error, PlainErrorMessage); + PORT_SetError(error); + return SECFailure; + } + + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return SECWouldBlock; +} -static struct OCSPDefaultResponders myDefaultOCSPResponders[] = { - /* COMODO */ - { - "CN=AddTrust External CA Root,OU=AddTrust External TTP Network,O=AddTrust AB,C=SE", - nsnull, "rb2YejS0Jvf6xCZU7wO94CTLVBo=", nsnull, - "http://ocsp.comodoca.com" - }, - { - "CN=COMODO Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", - nsnull, "C1jli8ZMFTekQKkwqSG+RzZaVv8=", nsnull, - "http://ocsp.comodoca.com" - }, - { - "CN=COMODO EV SGC CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", - nsnull, "f/ZMNigUrs0eN6/eWvJbw6CsK/4=", nsnull, - "http://ocsp.comodoca.com" - }, - { - "CN=COMODO EV SSL CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", - nsnull, "aRZJ7LZ1ZFrpAyNgL1RipTRcPuI=", nsnull, - "http://ocsp.comodoca.com" - }, - { - "CN=UTN - DATACorp SGC,OU=http://www.usertrust.com,O=The USERTRUST Network,L=Salt Lake City,ST=UT,C=US", - nsnull, "UzLRs89/+uDxoF2FTpLSnkUdtE8=", nsnull, - "http://ocsp.usertrust.com" - }, - { - "CN=UTN-USERFirst-Hardware,OU=http://www.usertrust.com,O=The USERTRUST Network,L=Salt Lake City,ST=UT,C=US", - nsnull, "oXJfJhsomEOVXQc31YWWnUvSw0U=", nsnull, - "http://ocsp.usertrust.com" - }, - /* Network Solutions */ - { - "CN=Network Solutions Certificate Authority,O=Network Solutions L.L.C.,C=US", - nsnull, "ITDJ+wDXTpjah6oq0KcusUAxp0w=", nsnull, - "http://ocsp.netsolssl.com" - }, - { - "CN=Network Solutions EV SSL CA,O=Network Solutions L.L.C.,C=US", - nsnull, "tk6FnYQfGx3UUolOB5Yt+d7xj8w=", nsnull, - "http://ocsp.netsolssl.com" - }, - /* GlobalSign */ - { - "CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE", - nsnull, "YHtmGkUNl8qJUC99BM00qP/8/Us=", nsnull, - "http://ocsp.globalsign.com/ExtendedSSLCACross" - }, - { - "CN=GlobalSign,O=GlobalSign,OU=GlobalSign Root CA - R2", - nsnull, "m+IHV2ccHsBqBt5ZtJot39wZhi4=", nsnull, - "http://ocsp.globalsign.com/ExtendedSSLCA" - }, - { - "CN=GlobalSign Extended Validation CA,O=GlobalSign,OU=Extended Validation CA", - nsnull, "NLH5yYxrNUTMCGkK7uOjuVy/FuA=", nsnull, - "http://ocsp.globalsign.com/ExtendedSSL" - }, - /* Trustwave */ - { - "CN=SecureTrust CA,O=SecureTrust Corporation,C=US", - nsnull, "QjK2FvoE/f5dS3rD/fdMQB1aQ68=", nsnull, - "http://ocsp.trustwave.com" - } -}; +NS_IMETHODIMP +SSLServerCertVerificationJob::Run() +{ + // Runs on a cert verification thread + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("[%p] SSLServerCertVerificationJob::Run\n", mSocketInfo.get())); + + PRErrorCode error; -static const unsigned int numResponders = - (sizeof myDefaultOCSPResponders) / (sizeof myDefaultOCSPResponders[0]); - -static CERT_StringFromCertFcn oldOCSPAIAInfoCallback = nsnull; + nsNSSShutDownPreventionLock nssShutdownPrevention; + if (mSocketInfo->isAlreadyShutDown()) { + error = SEC_ERROR_USER_CANCELLED; + } else { + // Reset the error code here so we can detect if AuthCertificate fails to + // set the error code if/when it fails. + PR_SetError(0, 0); + SECStatus rv = AuthCertificate(nssShutdownPrevention); + if (rv == SECSuccess) { + nsRefPtr<SSLServerCertVerificationResult> restart + = new SSLServerCertVerificationResult(*mSocketInfo, 0); + restart->Dispatch(); + return NS_OK; + } -/* - * See if we have a hard-coded default responder for this certificate's - * issuer (unless this certificate is a root certificate). - * - * The result needs to be freed (PORT_Free) when no longer in use. - */ -char* PR_CALLBACK MyAlternateOCSPAIAInfoCallback(CERTCertificate *cert) { - if (cert && !cert->isRoot) { - unsigned int i; - for (i=0; i < numResponders; i++) { - if (!(myDefaultOCSPResponders[i].issuerName)); - else if (!(myDefaultOCSPResponders[i].issuerKeyID)); - else if (!(cert->authKeyID)); - else if (CERT_CompareName(myDefaultOCSPResponders[i].issuerName, - &(cert->issuer)) != SECEqual); - else if (SECITEM_CompareItem(myDefaultOCSPResponders[i].issuerKeyID, - &(cert->authKeyID->keyID)) != SECEqual); - else // Issuer Name and Key Identifier match, so use this OCSP URL. - return PORT_Strdup(myDefaultOCSPResponders[i].ocspUrl); + error = PR_GetError(); + if (error != 0) { + rv = HandleBadCertificate(error, mSocketInfo, *mCert, mFdForLogging, + nssShutdownPrevention); + if (rv == SECSuccess) { + // The CertErrorRunnable will run on the main thread and it will dispatch + // the cert verification result to the socket transport thread, so we + // don't have to. This way, this verification thread doesn't need to + // wait for the CertErrorRunnable to complete. + return NS_OK; + } + // DispatchCertErrorRunnable set a new error code. + error = PR_GetError(); } } - // If we've not found a hard-coded default responder, chain to the old - // callback function (if there is one). - if (oldOCSPAIAInfoCallback) - return (*oldOCSPAIAInfoCallback)(cert); - - return nsnull; -} - -void cleanUpMyDefaultOCSPResponders() { - unsigned int i; + if (error == 0) { + NS_NOTREACHED("no error set during certificate validation failure"); + error = PR_INVALID_STATE_ERROR; + } - for (i=0; i < numResponders; i++) { - if (myDefaultOCSPResponders[i].issuerName) { - CERT_DestroyName(myDefaultOCSPResponders[i].issuerName); - myDefaultOCSPResponders[i].issuerName = nsnull; - } - if (myDefaultOCSPResponders[i].issuerKeyID) { - SECITEM_FreeItem(myDefaultOCSPResponders[i].issuerKeyID, true); - myDefaultOCSPResponders[i].issuerKeyID = nsnull; - } - } + nsRefPtr<SSLServerCertVerificationResult> failure + = new SSLServerCertVerificationResult(*mSocketInfo, error); + failure->Dispatch(); + return NS_OK; } -SECStatus RegisterMyOCSPAIAInfoCallback() { - // Prevent multiple registrations. - if (myDefaultOCSPResponders[0].issuerName) - return SECSuccess; // Already registered ok. +} // unnamed namespace + +// Extracts whatever information we need out of fd (using SSL_*) and passes it +// to SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob should +// never do anything with fd except logging. +SECStatus +AuthCertificateHook(void *arg, PRFileDesc *fd, PRBool checkSig, PRBool isServer) +{ + // Runs on the socket transport thread + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("[%p] starting AuthCertificateHook\n", fd)); + + // Modern libssl always passes PR_TRUE for checkSig, and we have no means of + // doing verification without checking signatures. + NS_ASSERTION(checkSig, "AuthCertificateHook: checkSig unexpectedly false"); - // Populate various fields in the myDefaultOCSPResponders[] array. - SECStatus rv = SECFailure; - unsigned int i; - for (i=0; i < numResponders; i++) { - // Create a CERTName structure from the issuer name string. - myDefaultOCSPResponders[i].issuerName = CERT_AsciiToName( - const_cast<char*>(myDefaultOCSPResponders[i].issuerName_string)); - if (!(myDefaultOCSPResponders[i].issuerName)) - goto loser; - // Create a SECItem from the Base64 authority key identifier keyID. - myDefaultOCSPResponders[i].issuerKeyID = NSSBase64_DecodeBuffer(nsnull, - nsnull, myDefaultOCSPResponders[i].issuerKeyID_base64, - (PRUint32)PORT_Strlen(myDefaultOCSPResponders[i].issuerKeyID_base64)); - if (!(myDefaultOCSPResponders[i].issuerKeyID)) - goto loser; + // PSM never causes libssl to call this function with PR_TRUE for isServer, + // and many things in PSM assume that we are a client. + NS_ASSERTION(!isServer, "AuthCertificateHook: isServer unexpectedly true"); + + if (!checkSig || isServer) { + PR_SetError(PR_INVALID_STATE_ERROR, 0); + return SECFailure; } + + CERTCertificate *serverCert = SSL_PeerCertificate(fd); - // Register our alternate OCSP Responder URL lookup function. - rv = CERT_RegisterAlternateOCSPAIAInfoCallBack(MyAlternateOCSPAIAInfoCallback, - &oldOCSPAIAInfoCallback); - if (rv != SECSuccess) - goto loser; + nsNSSSocketInfo *socketInfo = static_cast<nsNSSSocketInfo*>(arg); + SECStatus rv = SSLServerCertVerificationJob::Dispatch( + static_cast<const void *>(fd), socketInfo, serverCert); - return SECSuccess; + CERT_DestroyCertificate(serverCert); -loser: - cleanUpMyDefaultOCSPResponders(); return rv; } -SECStatus UnregisterMyOCSPAIAInfoCallback() { - SECStatus rv; - - // Only allow unregistration if we're already registered. - if (!(myDefaultOCSPResponders[0].issuerName)) - return SECFailure; +SSLServerCertVerificationResult::SSLServerCertVerificationResult( + nsNSSSocketInfo & socketInfo, PRErrorCode errorCode, + SSLErrorMessageType errorMessageType) + : mSocketInfo(&socketInfo) + , mErrorCode(errorCode) + , mErrorMessageType(errorMessageType) +{ +} - // Unregister our alternate OCSP Responder URL lookup function. - rv = CERT_RegisterAlternateOCSPAIAInfoCallBack(oldOCSPAIAInfoCallback, - nsnull); - if (rv != SECSuccess) - return rv; +void +SSLServerCertVerificationResult::Dispatch() +{ + nsresult rv; + nsCOMPtr<nsIEventTarget> stsTarget + = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + NS_ASSERTION(stsTarget, + "Failed to get socket transport service event target"); + rv = stsTarget->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to dispatch SSLServerCertVerificationResult"); +} - // Tidy up. - oldOCSPAIAInfoCallback = nsnull; - cleanUpMyDefaultOCSPResponders(); - return SECSuccess; +NS_IMETHODIMP +SSLServerCertVerificationResult::Run() +{ + // TODO: Assert that we're on the socket transport thread + mSocketInfo->SetCertVerificationResult(mErrorCode, mErrorMessageType); + return NS_OK; } + +} } // namespace mozilla::psm
new file mode 100644 --- /dev/null +++ b/security/manager/ssl/src/SSLServerCertVerification.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#ifndef _SSLSERVERCERTVERIFICATION_H +#define _SSLSERVERCERTVERIFICATION_H + +#include "seccomon.h" +#include "nsAutoPtr.h" +#include "nsThreadUtils.h" +#include "nsIRunnable.h" +#include "prerror.h" +#include "nsNSSIOLayer.h" + +typedef struct PRFileDesc PRFileDesc; +typedef struct CERTCertificateStr CERTCertificate; +class nsNSSSocketInfo; +class nsNSSShutDownPreventionLock; + +namespace mozilla { namespace psm { + +SECStatus AuthCertificateHook(void *arg, PRFileDesc *fd, + PRBool checkSig, PRBool isServer); + +SECStatus HandleBadCertificate(PRErrorCode defaultErrorCodeToReport, + nsNSSSocketInfo * socketInfo, + CERTCertificate & cert, + const void * fdForLogging, + const nsNSSShutDownPreventionLock &); + +// Dispatched from a cert verification thread to the STS thread to notify the +// socketInfo of the verification result. +// +// This will cause the PR_Poll in the STS thread to return, so things work +// correctly even if the STS thread is blocked polling (only) on the file +// descriptor that is waiting for this result. +class SSLServerCertVerificationResult : public nsRunnable +{ +public: + NS_DECL_NSIRUNNABLE + + SSLServerCertVerificationResult(nsNSSSocketInfo & socketInfo, + PRErrorCode errorCode, + SSLErrorMessageType errorMessageType = + PlainErrorMessage); + + void Dispatch(); +private: + const nsRefPtr<nsNSSSocketInfo> mSocketInfo; + const PRErrorCode mErrorCode; + const SSLErrorMessageType mErrorMessageType; +}; + +} } // namespace mozilla::psm + +#endif
--- a/security/manager/ssl/src/nsNSSCallbacks.cpp +++ b/security/manager/ssl/src/nsNSSCallbacks.cpp @@ -35,58 +35,39 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -#include "nsNSSComponent.h" // for PIPNSS string bundle calls. +#include "nsNSSComponent.h" #include "nsNSSCallbacks.h" -#include "nsNSSCertificate.h" -#include "nsNSSCleaner.h" -#include "nsSSLStatus.h" -#include "nsNSSIOLayer.h" // for nsNSSSocketInfo +#include "nsNSSIOLayer.h" #include "nsIWebProgressListener.h" -#include "nsIStringBundle.h" -#include "nsXPIDLString.h" -#include "nsCOMPtr.h" -#include "nsAutoPtr.h" -#include "nsIServiceManager.h" -#include "nsReadableUtils.h" -#include "nsIPrompt.h" -#include "nsIInterfaceRequestor.h" -#include "nsIInterfaceRequestorUtils.h" #include "nsProtectedAuthThread.h" #include "nsITokenDialogs.h" -#include "nsCRT.h" #include "nsNSSShutDown.h" #include "nsIUploadChannel.h" -#include "nsSSLThread.h" #include "nsThreadUtils.h" -#include "nsIThread.h" -#include "nsIWindowWatcher.h" #include "nsIPrompt.h" #include "nsProxyRelease.h" #include "PSMRunnable.h" #include "nsIConsoleService.h" +#include "nsIHttpChannelInternal.h" #include "ssl.h" -#include "cert.h" #include "ocsp.h" #include "nssb64.h" -#include "secerr.h" -#include "sslerr.h" using namespace mozilla; using namespace mozilla::psm; static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); -NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate) #ifdef PR_LOGGING extern PRLogModuleInfo* gPIPNSSLog; #endif class nsHTTPDownloadEvent : public nsRunnable { public: nsHTTPDownloadEvent(); @@ -148,16 +129,26 @@ nsHTTPDownloadEvent::Run() NS_ENSURE_STATE(uploadChannel); rv = uploadChannel->SetUploadStream(uploadStream, mRequestSession->mPostContentType, -1); NS_ENSURE_SUCCESS(rv, rv); } + // Do not use SPDY for internal security operations. It could result + // in the silent upgrade to ssl, which in turn could require an SSL + // operation to fufill something like a CRL fetch, which is an + // endless loop. + nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(chan); + if (internalChannel) { + rv = internalChannel->SetAllowSpdy(false); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCOMPtr<nsIHttpChannel> hchan = do_QueryInterface(chan); NS_ENSURE_STATE(hchan); rv = hchan->SetRequestMethod(mRequestSession->mRequestMethod); NS_ENSURE_SUCCESS(rv, rv); mResponsibleForDoneSignal = false; mListener->mResponsibleForDoneSignal = true; @@ -434,21 +425,20 @@ nsNSSHttpRequestSession::internal_send_r waitCondition.Wait(wait_interval); if (!waitFlag) break; if (!request_canceled) { - bool wantExit = nsSSLThread::stoppedOrStopping(); bool timeout = (PRIntervalTime)(PR_IntervalNow() - start_time) > mTimeoutInterval; - - if (wantExit || timeout) + + if (timeout) { request_canceled = true; nsRefPtr<nsCancelHTTPDownloadEvent> cancelevent = new nsCancelHTTPDownloadEvent; cancelevent->mListener = mListener; rv = NS_DispatchToMainThread(cancelevent); if (NS_FAILED(rv)) { NS_WARNING("cannot post cancel event"); @@ -835,16 +825,22 @@ void PR_CALLBACK HandshakeCallback(PRFil nsNSSShutDownPreventionLock locker; PRInt32 sslStatus; char* signer = nsnull; char* cipherName = nsnull; PRInt32 keyLength; nsresult rv; PRInt32 encryptBits; + nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; + + // If the handshake completed, then we know the site is TLS tolerant (if this + // was a TLS connection). + nsSSLIOLayerHelpers::rememberTolerantSite(fd, infoObject); + if (SECSuccess != SSL_SecurityStatus(fd, &sslStatus, &cipherName, &keyLength, &encryptBits, &signer, nsnull)) { return; } PRInt32 secStatus; if (sslStatus == SSL_SECURITY_STATUS_OFF) secStatus = nsIWebProgressListener::STATE_IS_BROKEN; @@ -856,17 +852,16 @@ void PR_CALLBACK HandshakeCallback(PRFil nsIWebProgressListener::STATE_SECURE_LOW); PRBool siteSupportsSafeRenego; if (SSL_HandshakeNegotiatedExtension(fd, ssl_renegotiation_info_xtn, &siteSupportsSafeRenego) != SECSuccess || !siteSupportsSafeRenego) { bool wantWarning = (nsSSLIOLayerHelpers::getWarnLevelMissingRFC5746() > 0); - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; nsCOMPtr<nsIConsoleService> console; if (infoObject && wantWarning) { console = do_GetService(NS_CONSOLESERVICE_CONTRACTID); if (console) { nsXPIDLCString hostName; infoObject->GetHostName(getter_Copies(hostName)); nsAutoString msg; @@ -953,325 +948,38 @@ void PR_CALLBACK HandshakeCallback(PRFil } } } status->mHaveKeyLengthAndCipher = true; status->mKeyLength = keyLength; status->mSecretKeyLength = encryptBits; status->mCipherName.Assign(cipherName); + + // Get the NPN value. Do this on the stack and copy it into + // a string rather than preallocating the string because right + // now we expect NPN to fail more often than it succeeds. + SSLNextProtoState state; + unsigned char npnbuf[256]; + unsigned int npnlen; + + if (SSL_GetNextProto(fd, &state, npnbuf, &npnlen, 256) == SECSuccess && + state == SSL_NEXT_PROTO_NEGOTIATED) + infoObject->SetNegotiatedNPN(reinterpret_cast<char *>(npnbuf), npnlen); + else + infoObject->SetNegotiatedNPN(nsnull, 0); + + infoObject->SetHandshakeCompleted(); } PORT_Free(cipherName); PR_FREEIF(certOrgName); PR_Free(signer); } -SECStatus -PSM_SSL_PKIX_AuthCertificate(PRFileDesc *fd, CERTCertificate *peerCert, bool checksig, bool isServer) -{ - SECStatus rv; - SECCertUsage certUsage; - SECCertificateUsage certificateusage; - void * pinarg; - char * hostname; - - pinarg = SSL_RevealPinArg(fd); - hostname = SSL_RevealURL(fd); - - /* this may seem backwards, but isn't. */ - certUsage = isServer ? certUsageSSLClient : certUsageSSLServer; - certificateusage = isServer ? certificateUsageSSLClient : certificateUsageSSLServer; - - if (!nsNSSComponent::globalConstFlagUsePKIXVerification) { - rv = CERT_VerifyCertNow(CERT_GetDefaultCertDB(), peerCert, checksig, certUsage, - pinarg); - } - else { - nsresult nsrv; - nsCOMPtr<nsINSSComponent> inss = do_GetService(kNSSComponentCID, &nsrv); - if (!inss) - return SECFailure; - nsRefPtr<nsCERTValInParamWrapper> survivingParams; - if (NS_FAILED(inss->GetDefaultCERTValInParam(survivingParams))) - return SECFailure; - - CERTValOutParam cvout[1]; - cvout[0].type = cert_po_end; - - rv = CERT_PKIXVerifyCert(peerCert, certificateusage, - survivingParams->GetRawPointerForNSS(), - cvout, pinarg); - } - - if ( rv == SECSuccess && !isServer ) { - /* cert is OK. This is the client side of an SSL connection. - * Now check the name field in the cert against the desired hostname. - * NB: This is our only defense against Man-In-The-Middle (MITM) attacks! - */ - if (hostname && hostname[0]) - rv = CERT_VerifyCertName(peerCert, hostname); - else - rv = SECFailure; - if (rv != SECSuccess) - PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); - } - - PORT_Free(hostname); - return rv; -} - -struct nsSerialBinaryBlacklistEntry -{ - unsigned int len; - const char *binary_serial; -}; - -// bug 642395 -static struct nsSerialBinaryBlacklistEntry myUTNBlacklistEntries[] = { - { 17, "\x00\x92\x39\xd5\x34\x8f\x40\xd1\x69\x5a\x74\x54\x70\xe1\xf2\x3f\x43" }, - { 17, "\x00\xd8\xf3\x5f\x4e\xb7\x87\x2b\x2d\xab\x06\x92\xe3\x15\x38\x2f\xb0" }, - { 16, "\x72\x03\x21\x05\xc5\x0c\x08\x57\x3d\x8e\xa5\x30\x4e\xfe\xe8\xb0" }, - { 17, "\x00\xb0\xb7\x13\x3e\xd0\x96\xf9\xb5\x6f\xae\x91\xc8\x74\xbd\x3a\xc0" }, - { 16, "\x39\x2a\x43\x4f\x0e\x07\xdf\x1f\x8a\xa3\x05\xde\x34\xe0\xc2\x29" }, - { 16, "\x3e\x75\xce\xd4\x6b\x69\x30\x21\x21\x88\x30\xae\x86\xa8\x2a\x71" }, - { 17, "\x00\xe9\x02\x8b\x95\x78\xe4\x15\xdc\x1a\x71\x0a\x2b\x88\x15\x44\x47" }, - { 17, "\x00\xd7\x55\x8f\xda\xf5\xf1\x10\x5b\xb2\x13\x28\x2b\x70\x77\x29\xa3" }, - { 16, "\x04\x7e\xcb\xe9\xfc\xa5\x5f\x7b\xd0\x9e\xae\x36\xe1\x0c\xae\x1e" }, - { 17, "\x00\xf5\xc8\x6a\xf3\x61\x62\xf1\x3a\x64\xf5\x4f\x6d\xc9\x58\x7c\x06" }, - { 0, 0 } // end marker -}; - -// Call this if we have already decided that a cert should be treated as INVALID, -// in order to check if we to worsen the error to REVOKED. -PRErrorCode -PSM_SSL_DigiNotarTreatAsRevoked(CERTCertificate * serverCert, - CERTCertList * serverCertChain) -{ - // If any involved cert was issued by DigiNotar, - // and serverCert was issued after 01-JUL-2011, - // then worsen the error to revoked. - - PRTime cutoff = 0; - PRStatus status = PR_ParseTimeString("01-JUL-2011 00:00", true, &cutoff); - if (status != PR_SUCCESS) { - NS_ASSERTION(status == PR_SUCCESS, "PR_ParseTimeString failed"); - // be safe, assume it's afterwards, keep going - } else { - PRTime notBefore = 0, notAfter = 0; - if (CERT_GetCertTimes(serverCert, ¬Before, ¬After) == SECSuccess && - notBefore < cutoff) { - // no worsening for certs issued before the cutoff date - return 0; - } - } - - for (CERTCertListNode *node = CERT_LIST_HEAD(serverCertChain); - !CERT_LIST_END(node, serverCertChain); - node = CERT_LIST_NEXT(node)) { - if (node->cert->issuerName && - strstr(node->cert->issuerName, "CN=DigiNotar")) { - return SEC_ERROR_REVOKED_CERTIFICATE; - } - } - - return 0; -} - -// Call this only if a cert has been reported by NSS as VALID -PRErrorCode -PSM_SSL_BlacklistDigiNotar(CERTCertificate * serverCert, - CERTCertList * serverCertChain) -{ - bool isDigiNotarIssuedCert = false; - - for (CERTCertListNode *node = CERT_LIST_HEAD(serverCertChain); - !CERT_LIST_END(node, serverCertChain); - node = CERT_LIST_NEXT(node)) { - if (!node->cert->issuerName) - continue; - - if (strstr(node->cert->issuerName, "CN=DigiNotar")) { - isDigiNotarIssuedCert = true; - } - } - - if (isDigiNotarIssuedCert) { - // let's see if we want to worsen the error code to revoked. - PRErrorCode revoked_code = PSM_SSL_DigiNotarTreatAsRevoked(serverCert, serverCertChain); - return (revoked_code != 0) ? revoked_code : SEC_ERROR_UNTRUSTED_ISSUER; - } - - return 0; -} - - -SECStatus PR_CALLBACK AuthCertificateCallback(void* client_data, PRFileDesc* fd, - PRBool checksig, PRBool isServer) { - nsNSSShutDownPreventionLock locker; - - CERTCertificate *serverCert = SSL_PeerCertificate(fd); - CERTCertificateCleaner serverCertCleaner(serverCert); - - if (serverCert && - serverCert->serialNumber.data && - serverCert->issuerName && - !strcmp(serverCert->issuerName, - "CN=UTN-USERFirst-Hardware,OU=http://www.usertrust.com,O=The USERTRUST Network,L=Salt Lake City,ST=UT,C=US")) { - - unsigned char *server_cert_comparison_start = (unsigned char*)serverCert->serialNumber.data; - unsigned int server_cert_comparison_len = serverCert->serialNumber.len; - - while (server_cert_comparison_len) { - if (*server_cert_comparison_start != 0) - break; - - ++server_cert_comparison_start; - --server_cert_comparison_len; - } - - nsSerialBinaryBlacklistEntry *walk = myUTNBlacklistEntries; - for ( ; walk && walk->len; ++walk) { - - unsigned char *locked_cert_comparison_start = (unsigned char*)walk->binary_serial; - unsigned int locked_cert_comparison_len = walk->len; - - while (locked_cert_comparison_len) { - if (*locked_cert_comparison_start != 0) - break; - - ++locked_cert_comparison_start; - --locked_cert_comparison_len; - } - - if (server_cert_comparison_len == locked_cert_comparison_len && - !memcmp(server_cert_comparison_start, locked_cert_comparison_start, locked_cert_comparison_len)) { - PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0); - return SECFailure; - } - } - } - - SECStatus rv = PSM_SSL_PKIX_AuthCertificate(fd, serverCert, checksig, isServer); - - // We want to remember the CA certs in the temp db, so that the application can find the - // complete chain at any time it might need it. - // But we keep only those CA certs in the temp db, that we didn't already know. - - if (serverCert) { - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; - nsRefPtr<nsSSLStatus> status = infoObject->SSLStatus(); - nsRefPtr<nsNSSCertificate> nsc; - - if (!status || !status->mServerCert) { - nsc = nsNSSCertificate::Create(serverCert); - } - - CERTCertList *certList = nsnull; - certList = CERT_GetCertChainFromCert(serverCert, PR_Now(), certUsageSSLCA); - if (!certList) { - rv = SECFailure; - } else { - PRErrorCode blacklistErrorCode; - if (rv == SECSuccess) { // PSM_SSL_PKIX_AuthCertificate said "valid cert" - blacklistErrorCode = PSM_SSL_BlacklistDigiNotar(serverCert, certList); - } else { // PSM_SSL_PKIX_AuthCertificate said "invalid cert" - PRErrorCode savedErrorCode = PORT_GetError(); - // Check if we want to worsen the error code to "revoked". - blacklistErrorCode = PSM_SSL_DigiNotarTreatAsRevoked(serverCert, certList); - if (blacklistErrorCode == 0) { - // we don't worsen the code, let's keep the original error code from NSS - PORT_SetError(savedErrorCode); - } - } - - if (blacklistErrorCode != 0) { - infoObject->SetCertIssuerBlacklisted(); - PORT_SetError(blacklistErrorCode); - rv = SECFailure; - } - } - - if (rv == SECSuccess) { - if (nsc) { - bool dummyIsEV; - nsc->GetIsExtendedValidation(&dummyIsEV); // the nsc object will cache the status - } - - nsCOMPtr<nsINSSComponent> nssComponent; - - for (CERTCertListNode *node = CERT_LIST_HEAD(certList); - !CERT_LIST_END(node, certList); - node = CERT_LIST_NEXT(node)) { - - if (node->cert->slot) { - // This cert was found on a token, no need to remember it in the temp db. - continue; - } - - if (node->cert->isperm) { - // We don't need to remember certs already stored in perm db. - continue; - } - - if (node->cert == serverCert) { - // We don't want to remember the server cert, - // the code that cares for displaying page info does this already. - continue; - } - - // We have found a signer cert that we want to remember. - char* nickname = nsNSSCertificate::defaultServerNickname(node->cert); - if (nickname && *nickname) { - PK11SlotInfo *slot = PK11_GetInternalKeySlot(); - if (slot) { - PK11_ImportCert(slot, node->cert, CK_INVALID_HANDLE, - nickname, false); - PK11_FreeSlot(slot); - } - } - PR_FREEIF(nickname); - } - - } - - if (certList) { - CERT_DestroyCertList(certList); - } - - // The connection may get terminated, for example, if the server requires - // a client cert. Let's provide a minimal SSLStatus - // to the caller that contains at least the cert and its status. - if (!status) { - status = new nsSSLStatus(); - infoObject->SetSSLStatus(status); - } - - if (rv == SECSuccess) { - // Certificate verification succeeded delete any potential record - // of certificate error bits. - nsSSLIOLayerHelpers::mHostsWithCertErrors->RememberCertHasError( - infoObject, nsnull, rv); - } - else { - // Certificate verification failed, update the status' bits. - nsSSLIOLayerHelpers::mHostsWithCertErrors->LookupCertErrorBits( - infoObject, status); - } - - if (status && !status->mServerCert) { - status->mServerCert = nsc; - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("AuthCertificateCallback setting NEW cert %p\n", status->mServerCert.get())); - } - } - - return rv; -} - struct OCSPDefaultResponders { const char *issuerName_string; CERTName *issuerName; const char *issuerKeyID_base64; SECItem *issuerKeyID; const char *ocspUrl; };
--- a/security/manager/ssl/src/nsNSSCallbacks.h +++ b/security/manager/ssl/src/nsNSSCallbacks.h @@ -47,21 +47,16 @@ #include "nsIStreamLoader.h" #include "mozilla/CondVar.h" #include "mozilla/Mutex.h" char* PR_CALLBACK PK11PasswordPrompt(PK11SlotInfo *slot, PRBool retry, void* arg); void PR_CALLBACK HandshakeCallback(PRFileDesc *fd, void *client_data); -SECStatus PR_CALLBACK AuthCertificateCallback(void* client_data, PRFileDesc* fd, - PRBool checksig, PRBool isServer); - -PRErrorCode PSM_SSL_BlacklistDigiNotar(CERTCertificate * serverCert, - CERTCertList * serverCertChain); SECStatus RegisterMyOCSPAIAInfoCallback(); SECStatus UnregisterMyOCSPAIAInfoCallback(); class nsHTTPListener : public nsIStreamLoaderObserver { private: // For XPCOM implementations that are not a base class for some other
--- a/security/manager/ssl/src/nsNSSComponent.cpp +++ b/security/manager/ssl/src/nsNSSComponent.cpp @@ -42,17 +42,16 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsNSSComponent.h" #include "nsNSSCallbacks.h" #include "nsNSSIOLayer.h" -#include "nsSSLThread.h" #include "nsCertVerificationThread.h" #include "nsNetUtil.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryService.h" #include "nsIStreamListener.h" #include "nsIStringBundle.h" #include "nsIDirectoryService.h" @@ -359,17 +358,17 @@ bool EnsureNSSInitialized(EnsureNSSOpera } } nsNSSComponent::nsNSSComponent() :mutex("nsNSSComponent.mutex"), mNSSInitialized(false), mCrlTimerLock("nsNSSComponent.mCrlTimerLock"), mThreadList(nsnull), - mSSLThread(NULL), mCertVerificationThread(NULL) + mCertVerificationThread(NULL) { #ifdef PR_LOGGING if (!gPIPNSSLog) gPIPNSSLog = PR_NewLogModule("pipnss"); #endif PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsNSSComponent::ctor\n")); mUpdateTimerInitialized = false; crlDownloadTimerOn = false; @@ -386,47 +385,32 @@ nsNSSComponent::nsNSSComponent() hashTableCerts = nsnull; mShutdownObjectList = nsNSSShutDownList::construct(); mIsNetworkDown = false; } void nsNSSComponent::deleteBackgroundThreads() { - if (mSSLThread) - { - mSSLThread->requestExit(); - delete mSSLThread; - mSSLThread = nsnull; - } if (mCertVerificationThread) { mCertVerificationThread->requestExit(); delete mCertVerificationThread; mCertVerificationThread = nsnull; } } void nsNSSComponent::createBackgroundThreads() { - NS_ASSERTION(mSSLThread == nsnull, "SSL thread already created."); NS_ASSERTION(mCertVerificationThread == nsnull, "Cert verification thread already created."); - mSSLThread = new nsSSLThread; - nsresult rv = mSSLThread->startThread(); - if (NS_FAILED(rv)) { - delete mSSLThread; - mSSLThread = nsnull; - return; - } - mCertVerificationThread = new nsCertVerificationThread; - rv = mCertVerificationThread->startThread(); + nsresult rv = mCertVerificationThread->startThread(); if (NS_FAILED(rv)) { delete mCertVerificationThread; mCertVerificationThread = nsnull; } } nsNSSComponent::~nsNSSComponent() { @@ -1994,17 +1978,17 @@ nsNSSComponent::Init() mPrefBranch->GetIntPref("security.ssl.warn_missing_rfc5746", &warnLevel); nsSSLIOLayerHelpers::setWarnLevelMissingRFC5746(warnLevel); mClientAuthRememberService = new nsClientAuthRememberService; if (mClientAuthRememberService) mClientAuthRememberService->Init(); createBackgroundThreads(); - if (!mSSLThread || !mCertVerificationThread) + if (!mCertVerificationThread) { PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("NSS init, could not create threads\n")); DeregisterObservers(); mPIPNSSBundle = nsnull; return NS_ERROR_OUT_OF_MEMORY; } @@ -2544,18 +2528,16 @@ nsNSSComponent::DoProfileApproveChange(n status->VetoChange(); } } } void nsNSSComponent::DoProfileChangeNetTeardown() { - if (mSSLThread) - mSSLThread->requestExit(); if (mCertVerificationThread) mCertVerificationThread->requestExit(); mIsNetworkDown = true; } void nsNSSComponent::DoProfileChangeTeardown(nsISupports* aSubject) {
--- a/security/manager/ssl/src/nsNSSComponent.h +++ b/security/manager/ssl/src/nsNSSComponent.h @@ -232,17 +232,16 @@ private: ~nsCryptoHMAC(); PK11Context* mHMACContext; virtual void virtualDestroyNSSReference(); void destructorSafeDestroyNSSReference(); }; class nsNSSShutDownList; -class nsSSLThread; class nsCertVerificationThread; // Implementation of the PSM component interface. class nsNSSComponent : public nsISignatureVerifier, public nsIEntropyCollector, public nsINSSComponent, public nsIObserver, public nsSupportsWeakReference, @@ -352,17 +351,16 @@ private: bool mUpdateTimerInitialized; static int mInstanceCount; nsNSSShutDownList *mShutdownObjectList; SmartCardThreadList *mThreadList; bool mIsNetworkDown; void deleteBackgroundThreads(); void createBackgroundThreads(); - nsSSLThread *mSSLThread; nsCertVerificationThread *mCertVerificationThread; nsNSSHttpInterface mHttpForNSS; nsRefPtr<nsClientAuthRememberService> mClientAuthRememberService; nsRefPtr<nsCERTValInParamWrapper> mDefaultCERTValInParam; nsRefPtr<nsCERTValInParamWrapper> mDefaultCERTValInParamLocalOnly; static PRStatus PR_CALLBACK IdentityInfoInit(void);
--- a/security/manager/ssl/src/nsNSSIOLayer.cpp +++ b/security/manager/ssl/src/nsNSSIOLayer.cpp @@ -65,17 +65,17 @@ #include "nsIStrictTransportSecurityService.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsHashSets.h" #include "nsCRT.h" #include "nsAutoPtr.h" #include "nsPrintfCString.h" -#include "nsSSLThread.h" +#include "SSLServerCertVerification.h" #include "nsNSSShutDown.h" #include "nsSSLStatus.h" #include "nsNSSCertHelper.h" #include "nsNSSCleaner.h" #include "nsThreadUtils.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsISecureBrowserUI.h" @@ -105,18 +105,18 @@ using namespace mozilla::psm; //#define DUMP_BUFFER //Enable this define along with //DEBUG_SSL_VERBOSE to dump SSL //read/write buffer to a log. //Uses PR_LOG except on Mac where //we always write out to our own //file. + NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate) -NSSCleanupAutoPtrClass(char, PL_strfree) NSSCleanupAutoPtrClass(void, PR_FREEIF) NSSCleanupAutoPtrClass_WithParam(PRArenaPool, PORT_FreeArena, FalseParam, false) static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); /* SSM_UserCertChoice: enum for cert choice info */ typedef enum {ASK, AUTO} SSM_UserCertChoice; @@ -145,90 +145,45 @@ void MyLogFunction(const char *fmt, ...) return; PR_vfprintf(gMyLogFile, fmt, ap); va_end(ap); } #define PR_LOG(module,level,args) MyLogFunction args #endif - -nsSSLSocketThreadData::nsSSLSocketThreadData() -: mSSLState(ssl_idle) -, mPRErrorCode(PR_SUCCESS) -, mSSLDataBuffer(nsnull) -, mSSLDataBufferAllocatedSize(0) -, mSSLRequestedTransferAmount(0) -, mSSLRemainingReadResultData(nsnull) -, mSSLResultRemainingBytes(0) -, mReplacedSSLFileDesc(nsnull) -, mOneBytePendingFromEarlierWrite(false) -, mThePendingByte(0) -, mOriginalRequestedTransferAmount(0) -{ -} - -nsSSLSocketThreadData::~nsSSLSocketThreadData() -{ - NS_ASSERTION(mSSLState != ssl_pending_write - && - mSSLState != ssl_pending_read, - "oops??? ssl socket is not idle at the time it is being destroyed"); - if (mSSLDataBuffer) { - nsMemory::Free(mSSLDataBuffer); - } -} - -bool nsSSLSocketThreadData::ensure_buffer_size(PRInt32 amount) -{ - if (amount > mSSLDataBufferAllocatedSize) { - if (mSSLDataBuffer) { - mSSLDataBuffer = (char*)nsMemory::Realloc(mSSLDataBuffer, amount); - } - else { - mSSLDataBuffer = (char*)nsMemory::Alloc(amount); - } - - if (!mSSLDataBuffer) - return false; - - mSSLDataBufferAllocatedSize = amount; - } - - return true; -} - nsNSSSocketInfo::nsNSSSocketInfo() - : mFd(nsnull), - mBlockingState(blocking_state_unknown), + : mMutex("nsNSSSocketInfo::nsNSSSocketInfo"), + mFd(nsnull), + mCertVerificationState(before_cert_verification), mSecurityState(nsIWebProgressListener::STATE_IS_INSECURE), mSubRequestsHighSecurity(0), mSubRequestsLowSecurity(0), mSubRequestsBrokenSecurity(0), mSubRequestsNoSecurity(0), - mDocShellDependentStuffKnown(false), - mExternalErrorReporting(false), + mErrorCode(0), + mErrorMessageType(PlainErrorMessage), mForSTARTTLS(false), mHandshakePending(true), - mCanceled(false), mHasCleartextPhase(false), mHandshakeInProgress(false), mAllowTLSIntoleranceTimeout(true), mRememberClientAuthCertificate(false), mHandshakeStartTime(0), mPort(0), - mIsCertIssuerBlacklisted(false) + mIsCertIssuerBlacklisted(false), + mNPNCompleted(false), + mHandshakeCompleted(false), + mJoined(false), + mSentClientCert(false) { - mThreadData = new nsSSLSocketThreadData; } nsNSSSocketInfo::~nsNSSSocketInfo() { - delete mThreadData; - nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) return; shutdown(calledFromObject); } void nsNSSSocketInfo::virtualDestroyNSSReference() @@ -282,24 +237,33 @@ nsNSSSocketInfo::SetPort(PRInt32 aPort) nsresult nsNSSSocketInfo::GetPort(PRInt32 *aPort) { *aPort = mPort; return NS_OK; } -void nsNSSSocketInfo::SetCanceled(bool aCanceled) +PRErrorCode +nsNSSSocketInfo::GetErrorCode() const { - mCanceled = aCanceled; + MutexAutoLock lock(mMutex); + + return mErrorCode; } -bool nsNSSSocketInfo::GetCanceled() +void +nsNSSSocketInfo::SetCanceled(PRErrorCode errorCode, + SSLErrorMessageType errorMessageType) { - return mCanceled; + MutexAutoLock lock(mMutex); + + mErrorCode = errorCode; + mErrorMessageType = errorMessageType; + mErrorMessageCached.Truncate(); } NS_IMETHODIMP nsNSSSocketInfo::GetRememberClientAuthCertificate(bool *aRememberClientAuthCertificate) { NS_ENSURE_ARG_POINTER(aRememberClientAuthCertificate); *aRememberClientAuthCertificate = mRememberClientAuthCertificate; return NS_OK; } @@ -332,17 +296,16 @@ NS_IMETHODIMP nsNSSSocketInfo::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) { if (!aCallbacks) { mCallbacks = nsnull; return NS_OK; } mCallbacks = aCallbacks; - mDocShellDependentStuffKnown = false; return NS_OK; } static void getSecureBrowserUI(nsIInterfaceRequestor * callbacks, nsISecureBrowserUI ** result) { @@ -368,33 +331,16 @@ getSecureBrowserUI(nsIInterfaceRequestor nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(rootItem); if (docShell) { (void) docShell->GetSecurityUI(result); } } } -// Are we running within a context that wants external SSL error reporting? -// We'll look at the presence of a security UI object inside docshell. -// If the docshell wants the lock icon, you'll get the ssl error pages, too. -// This is helpful to distinguish from all other contexts, like mail windows, -// or any other SSL connections running in the background. -bool -nsNSSSocketInfo::GetExternalErrorReporting() -{ - NS_ASSERTION(NS_IsMainThread(), - "nsNSSSocketInfo::GetExternalErrorReporting called off the " - "main thread."); - - nsCOMPtr<nsISecureBrowserUI> secureUI; - getSecureBrowserUI(mCallbacks, getter_AddRefs(secureUI)); - return secureUI != nsnull; -} - NS_IMETHODIMP nsNSSSocketInfo::GetSecurityState(PRUint32* state) { *state = mSecurityState; return NS_OK; } nsresult @@ -469,29 +415,160 @@ nsNSSSocketInfo::GetShortSecurityDescrip nsresult nsNSSSocketInfo::SetShortSecurityDescription(const PRUnichar* aText) { mShortDesc.Assign(aText); return NS_OK; } NS_IMETHODIMP -nsNSSSocketInfo::GetErrorMessage(PRUnichar** aText) { - if (mErrorMessage.IsEmpty()) - *aText = nsnull; - else { - *aText = ToNewUnicode(mErrorMessage); - NS_ENSURE_TRUE(*aText, NS_ERROR_OUT_OF_MEMORY); +nsNSSSocketInfo::GetErrorMessage(PRUnichar** aText) +{ + NS_ENSURE_ARG_POINTER(aText); + *aText = nsnull; + + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSSocketInfo::GetErrorMessage called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; } - return NS_OK; + + MutexAutoLock lock(mMutex); + + nsresult rv = formatErrorMessage(lock); + NS_ENSURE_SUCCESS(rv, rv); + + *aText = ToNewUnicode(mErrorMessageCached); + return *aText != nsnull ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } void -nsNSSSocketInfo::SetErrorMessage(const PRUnichar* aText) { - mErrorMessage.Assign(aText); +nsNSSSocketInfo::SetNegotiatedNPN(const char *value, PRUint32 length) +{ + if (!value) + mNegotiatedNPN.Truncate(); + else + mNegotiatedNPN.Assign(value, length); + mNPNCompleted = true; +} + +NS_IMETHODIMP +nsNSSSocketInfo::GetNegotiatedNPN(nsACString &aNegotiatedNPN) +{ + if (!mNPNCompleted) + return NS_ERROR_NOT_CONNECTED; + + aNegotiatedNPN = mNegotiatedNPN; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSSocketInfo::JoinConnection(const nsACString & npnProtocol, + const nsACString & hostname, + PRInt32 port, + bool *_retval NS_OUTPARAM) +{ + *_retval = false; + + // Different ports may not be joined together + if (port != mPort) + return NS_OK; + + // Make sure NPN has been completed and matches requested npnProtocol + if (!mNPNCompleted || !mNegotiatedNPN.Equals(npnProtocol)) + return NS_OK; + + // If this is the same hostname then the certicate status does not + // need to be considered. They are joinable. + if (mHostName && hostname.Equals(mHostName)) { + *_retval = true; + return NS_OK; + } + + // Before checking the server certificate we need to make sure the + // handshake has completed. + if (!mHandshakeCompleted || !SSLStatus() || !SSLStatus()->mServerCert) + return NS_OK; + + // If the cert has error bits (e.g. it is untrusted) then do not join. + // The value of mHaveCertErrorBits is only reliable because we know that + // the handshake completed. + if (SSLStatus()->mHaveCertErrorBits) + return NS_OK; + + // If the connection is using client certificates then do not join + // because the user decides on whether to send client certs to hosts on a + // per-domain basis. + if (mSentClientCert) + return NS_OK; + + // Ensure that the server certificate covers the hostname that would + // like to join this connection + + CERTCertificate *nssCert = nsnull; + CERTCertificateCleaner nsscertCleaner(nssCert); + + nsCOMPtr<nsIX509Cert2> cert2 = do_QueryInterface(SSLStatus()->mServerCert); + if (cert2) + nssCert = cert2->GetCert(); + + if (!nssCert) + return NS_OK; + + if (CERT_VerifyCertName(nssCert, PromiseFlatCString(hostname).get()) != + SECSuccess) + return NS_OK; + + // All tests pass - this is joinable + mJoined = true; + *_retval = true; + return NS_OK; +} + +static nsresult +formatPlainErrorMessage(nsXPIDLCString const & host, PRInt32 port, + PRErrorCode err, nsString &returnedMessage); + +static nsresult +formatOverridableCertErrorMessage(nsISSLStatus & sslStatus, + PRErrorCode errorCodeToReport, + const nsXPIDLCString & host, PRInt32 port, + nsString & returnedMessage); + +// XXX: uses nsNSSComponent string bundles off the main thread when called by +// nsNSSSocketInfo::Write(). When we remove the error message from the +// serialization of nsNSSSocketInfo (bug 697781) we can inline +// formatErrorMessage into GetErrorMessage(). +nsresult +nsNSSSocketInfo::formatErrorMessage(MutexAutoLock const & proofOfLock) +{ + if (mErrorCode == 0 || !mErrorMessageCached.IsEmpty()) { + return NS_OK; + } + + nsresult rv; + NS_ConvertASCIItoUTF16 hostNameU(mHostName); + NS_ASSERTION(mErrorMessageType != OverridableCertErrorMessage || + (mSSLStatus && mSSLStatus->mServerCert && + mSSLStatus->mHaveCertErrorBits), + "GetErrorMessage called for cert error without cert"); + if (mErrorMessageType == OverridableCertErrorMessage && + mSSLStatus && mSSLStatus->mServerCert) { + rv = formatOverridableCertErrorMessage(*mSSLStatus, mErrorCode, + mHostName, mPort, + mErrorMessageCached); + } else { + rv = formatPlainErrorMessage(mHostName, mPort, mErrorCode, + mErrorMessageCached); + } + + if (NS_FAILED(rv)) { + mErrorMessageCached.Truncate(); + } + + return rv; } /* void getInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */ NS_IMETHODIMP nsNSSSocketInfo::GetInterface(const nsIID & uuid, void * *result) { if (!NS_IsMainThread()) { NS_ERROR("nsNSSSocketInfo::GetInterface called off the main thread"); return NS_ERROR_NOT_SAME_THREAD; @@ -500,19 +577,16 @@ NS_IMETHODIMP nsNSSSocketInfo::GetInterf nsresult rv; if (!mCallbacks) { nsCOMPtr<nsIInterfaceRequestor> ir = new PipUIContext(); if (!ir) return NS_ERROR_OUT_OF_MEMORY; rv = ir->GetInterface(uuid, result); } else { - if (nsSSLThread::stoppedOrStopping()) - return NS_ERROR_FAILURE; - rv = mCallbacks->GetInterface(uuid, result); } return rv; } nsresult nsNSSSocketInfo::GetForSTARTTLS(bool* aForSTARTTLS) { @@ -534,25 +608,57 @@ nsNSSSocketInfo::ProxyStartSSL() } NS_IMETHODIMP nsNSSSocketInfo::StartTLS() { return ActivateSSL(); } +NS_IMETHODIMP +nsNSSSocketInfo::SetNPNList(nsTArray<nsCString> &protocolArray) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + if (!mFd) + return NS_ERROR_FAILURE; + + // the npn list is a concatenated list of 8 bit byte strings. + nsCString npnList; + + for (PRUint32 index = 0; index < protocolArray.Length(); ++index) { + if (protocolArray[index].IsEmpty() || + protocolArray[index].Length() > 255) + return NS_ERROR_ILLEGAL_VALUE; + + npnList.Append(protocolArray[index].Length()); + npnList.Append(protocolArray[index]); + } + + if (SSL_SetNextProtoNego( + mFd, + reinterpret_cast<const unsigned char *>(npnList.get()), + npnList.Length()) != SECSuccess) + return NS_ERROR_FAILURE; + + return NS_OK; +} + static NS_DEFINE_CID(kNSSCertificateCID, NS_X509CERT_CID); #define NSSSOCKETINFOMAGIC { 0xa9863a23, 0x26b8, 0x4a9c, \ { 0x83, 0xf1, 0xe9, 0xda, 0xdb, 0x36, 0xb8, 0x30 } } static NS_DEFINE_CID(kNSSSocketInfoMagic, NSSSOCKETINFOMAGIC); NS_IMETHODIMP nsNSSSocketInfo::Write(nsIObjectOutputStream* stream) { stream->WriteID(kNSSSocketInfoMagic); + MutexAutoLock lock(mMutex); + nsRefPtr<nsSSLStatus> status = mSSLStatus; nsCOMPtr<nsISerializable> certSerializable; // Write a redundant copy of the certificate for backward compatibility // with previous versions, which also unnecessarily wrote it. // // As we are reading the object our self, not using ReadObject, we have // to store it here 'manually' as well, mimicking our object stream @@ -583,17 +689,21 @@ nsNSSSocketInfo::Write(nsIObjectOutputSt // to distinguish version number from mSecurityState // field stored in times before versioning has been introduced. // This mask value has been chosen as mSecurityState could // never be assigned such value. PRUint32 version = 3; stream->Write32(version | 0xFFFF0000); stream->Write32(mSecurityState); stream->WriteWStringZ(mShortDesc.get()); - stream->WriteWStringZ(mErrorMessage.get()); + + // XXX: uses nsNSSComponent string bundles off the main thread + nsresult rv = formatErrorMessage(lock); + NS_ENSURE_SUCCESS(rv, rv); + stream->WriteWStringZ(mErrorMessageCached.get()); stream->WriteCompoundObject(NS_ISUPPORTS_CAST(nsISSLStatus*, status), NS_GET_IID(nsISupports), true); stream->Write32((PRUint32)mSubRequestsHighSecurity); stream->Write32((PRUint32)mSubRequestsLowSecurity); stream->Write32((PRUint32)mSubRequestsBrokenSecurity); stream->Write32((PRUint32)mSubRequestsNoSecurity); @@ -663,29 +773,32 @@ nsNSSSocketInfo::Read(nsIObjectInputStre // as we did before. stream->Read32(&version); } else { // There seems not to be the certificate present in the stream. version = UUID_0; } + MutexAutoLock lock(mMutex); + // If the version field we have just read is not masked with 0xFFFF0000 // then it is stored mSecurityState field and this is version 1 of // the binary data stream format. if ((version & 0xFFFF0000) == 0xFFFF0000) { version &= ~0xFFFF0000; stream->Read32(&mSecurityState); } else { mSecurityState = version; version = 1; } stream->ReadString(mShortDesc); - stream->ReadString(mErrorMessage); + stream->ReadString(mErrorMessageCached); + mErrorCode = 0; nsCOMPtr<nsISupports> obj; stream->ReadObject(true, getter_AddRefs(obj)); mSSLStatus = reinterpret_cast<nsSSLStatus*>(obj.get()); if (!mSSLStatus) { NS_WARNING("deserializing nsNSSSocketInfo without mSSLStatus"); @@ -768,20 +881,20 @@ nsNSSSocketInfo::GetClassIDNoAlloc(nsCID } nsresult nsNSSSocketInfo::ActivateSSL() { nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) return NS_ERROR_NOT_AVAILABLE; - nsresult rv = nsSSLThread::requestActivateSSL(this); - - if (NS_FAILED(rv)) - return rv; + if (SECSuccess != SSL_OptionSet(mFd, SSL_SECURITY, true)) + return NS_ERROR_FAILURE; + if (SECSuccess != SSL_ResetHandshake(mFd, false)) + return NS_ERROR_FAILURE; mHandshakePending = true; return NS_OK; } nsresult nsNSSSocketInfo::GetFileDescPtr(PRFileDesc** aFilePtr) { @@ -833,16 +946,54 @@ void nsNSSSocketInfo::GetPreviousCert(ns } nsRefPtr<PreviousCertRunnable> runnable = new PreviousCertRunnable(mCallbacks); nsresult rv = runnable->DispatchToMainThreadAndWait(); NS_ASSERTION(NS_SUCCEEDED(rv), "runnable->DispatchToMainThreadAndWait() failed"); runnable->mPreviousCert.forget(_result); } +void +nsNSSSocketInfo::SetCertVerificationWaiting() +{ + // mCertVerificationState may be before_cert_verification for the first + // handshake on the connection, or after_cert_verification for subsequent + // renegotiation handshakes. + NS_ASSERTION(mCertVerificationState != waiting_for_cert_verification, + "Invalid state transition to waiting_for_cert_verification"); + mCertVerificationState = waiting_for_cert_verification; +} + +void +nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode, + SSLErrorMessageType errorMessageType) +{ + NS_ASSERTION(mCertVerificationState == waiting_for_cert_verification, + "Invalid state transition to cert_verification_finished"); + + if (errorCode != 0) { + SetCanceled(errorCode, errorMessageType); + } else if (mFd) { + // We haven't closed the connection already, so restart it + SECStatus rv = SSL_RestartHandshakeAfterAuthCertificate(mFd); + if (rv != SECSuccess) { + errorCode = PR_GetError(); + if (errorCode == 0) { + NS_ERROR("SSL_RestartHandshakeAfterAuthCertificate didn't set error code"); + errorCode = PR_INVALID_STATE_ERROR; + } + SetCanceled(errorCode, PlainErrorMessage); + } + } else { + // If we closed the connection alreay, we don't have anything to do + } + + mCertVerificationState = after_cert_verification; +} + nsresult nsNSSSocketInfo::GetSSLStatus(nsISSLStatus** _result) { NS_ENSURE_ARG_POINTER(_result); *_result = mSSLStatus; NS_IF_ADDREF(*_result); return NS_OK; @@ -893,62 +1044,59 @@ void nsSSLIOLayerHelpers::Cleanup() mTLSTolerantSites = nsnull; } if (mRenegoUnrestrictedSites) { delete mRenegoUnrestrictedSites; mRenegoUnrestrictedSites = nsnull; } - if (mSharedPollableEvent) - PR_DestroyPollableEvent(mSharedPollableEvent); - if (mutex) { delete mutex; mutex = nsnull; } if (mHostsWithCertErrors) { delete mHostsWithCertErrors; mHostsWithCertErrors = nsnull; } } +/* Formats an error message for non-certificate-related SSL errors + * and non-overridable certificate errors (both are of type + * PlainErrormMessage). Use formatOverridableCertErrorMessage + * for overridable cert errors. + */ static nsresult -getErrorMessage(PRInt32 err, - const nsString &host, - PRInt32 port, - nsINSSComponent *component, - nsString &returnedMessage) +formatPlainErrorMessage(const nsXPIDLCString &host, PRInt32 port, + PRErrorCode err, nsString &returnedMessage) { - NS_ENSURE_ARG_POINTER(component); - const PRUnichar *params[1]; nsresult rv; + nsCOMPtr<nsINSSComponent> component = do_GetService(kNSSComponentCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (host.Length()) { nsString hostWithPort; // For now, hide port when it's 443 and we're reporting the error. // In the future a better mechanism should be used // to make a decision about showing the port number, possibly by requiring // the context object to implement a specific interface. // The motivation is that Mozilla browser would like to hide the port number // in error pages in the common case. - if (port == 443) { - params[0] = host.get(); - } - else { - hostWithPort = host; + hostWithPort.AssignASCII(host); + if (port != 443) { hostWithPort.AppendLiteral(":"); hostWithPort.AppendInt(port); - params[0] = hostWithPort.get(); } + params[0] = hostWithPort.get(); nsString formattedString; rv = component->PIPBundleFormatStringFromName("SSLConnectionErrorPrefix", params, 1, formattedString); if (NS_SUCCEEDED(rv)) { returnedMessage.Append(formattedString); @@ -1297,187 +1445,188 @@ AppendErrorTextCode(PRErrorCode errorCod else { returnedMessage.Append(NS_LITERAL_STRING(" (")); returnedMessage.Append(idU); returnedMessage.Append(NS_LITERAL_STRING(")")); } } } -static void -getInvalidCertErrorMessage(PRUint32 multipleCollectedErrors, - PRErrorCode errorCodeToReport, - PRErrorCode errTrust, - PRErrorCode errMismatch, - PRErrorCode errExpired, - const nsString &host, - const nsString &hostWithPort, - PRInt32 port, - nsIX509Cert* ix509, - nsString &returnedMessage) +/* Formats an error message for overridable certificate errors (of type + * OverridableCertErrorMessage). Use formatPlainErrorMessage to format + * non-overridable cert errors and non-cert-related errors. + */ +static nsresult +formatOverridableCertErrorMessage(nsISSLStatus & sslStatus, + PRErrorCode errorCodeToReport, + const nsXPIDLCString & host, PRInt32 port, + nsString & returnedMessage) { const PRUnichar *params[1]; nsresult rv; + nsAutoString hostWithPort; + nsAutoString hostWithoutPort; // For now, hide port when it's 443 and we're reporting the error. // In the future a better mechanism should be used // to make a decision about showing the port number, possibly by requiring // the context object to implement a specific interface. // The motivation is that Mozilla browser would like to hide the port number // in error pages in the common case. - if (port == 443) - params[0] = host.get(); - else + hostWithoutPort.AppendASCII(host); + if (port == 443) { + params[0] = hostWithoutPort.get(); + } else { + hostWithPort.AppendASCII(host); + hostWithPort.Append(':'); + hostWithPort.AppendInt(port); params[0] = hostWithPort.get(); + } nsCOMPtr<nsINSSComponent> component = do_GetService(kNSSComponentCID, &rv); - if (NS_FAILED(rv)) - return; - - nsString formattedString; + NS_ENSURE_SUCCESS(rv, rv); + + returnedMessage.Truncate(); rv = component->PIPBundleFormatStringFromName("certErrorIntro", params, 1, - formattedString); - if (NS_SUCCEEDED(rv)) - { - returnedMessage.Append(formattedString); - returnedMessage.Append(NS_LITERAL_STRING("\n\n")); - } - - if (multipleCollectedErrors & nsICertOverrideService::ERROR_UNTRUSTED) - { - AppendErrorTextUntrusted(errTrust, host, ix509, + returnedMessage); + NS_ENSURE_SUCCESS(rv, rv); + + returnedMessage.Append(NS_LITERAL_STRING("\n\n")); + + nsRefPtr<nsIX509Cert> ix509; + rv = sslStatus.GetServerCert(getter_AddRefs(ix509)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isUntrusted; + rv = sslStatus.GetIsUntrusted(&isUntrusted); + NS_ENSURE_SUCCESS(rv, rv); + if (isUntrusted) { + AppendErrorTextUntrusted(errorCodeToReport, hostWithoutPort, ix509, component, returnedMessage); } - if (multipleCollectedErrors & nsICertOverrideService::ERROR_MISMATCH) - { - AppendErrorTextMismatch(host, ix509, component, returnedMessage); + bool isDomainMismatch; + rv = sslStatus.GetIsDomainMismatch(&isDomainMismatch); + NS_ENSURE_SUCCESS(rv, rv); + if (isDomainMismatch) { + AppendErrorTextMismatch(hostWithoutPort, ix509, component, returnedMessage); } - if (multipleCollectedErrors & nsICertOverrideService::ERROR_TIME) - { + bool isNotValidAtThisTime; + rv = sslStatus.GetIsNotValidAtThisTime(&isNotValidAtThisTime); + NS_ENSURE_SUCCESS(rv, rv); + if (isNotValidAtThisTime) { AppendErrorTextTime(ix509, component, returnedMessage); } AppendErrorTextCode(errorCodeToReport, component, returnedMessage); + + return NS_OK; } static void -nsHandleSSLError(nsNSSSocketInfo *socketInfo, PRInt32 err) +nsHandleSSLError(nsNSSSocketInfo *socketInfo, PRErrorCode err) { if (!NS_IsMainThread()) { NS_ERROR("nsHandleSSLError called off the main thread"); return; } - // SetCanceled is only called by the main thread or the SSL thread. Whenever - // this function is called, the SSL thread is waiting on this thread (the - // main thread). So, no mutex is necessary for SetCanceled()/GetCanceled(). - if (socketInfo->GetCanceled()) { + // SetCanceled is only called by the main thread or the socket transport + // thread. Whenever this function is called on the main thread, the SSL + // thread is blocked on it. So, no mutex is necessary for + // SetCanceled()/GetError*(). + if (socketInfo->GetErrorCode()) { // If the socket has been flagged as canceled, - // the code who did was responsible for showing - // an error message (if desired). - return; - } - - if (nsSSLThread::stoppedOrStopping()) { + // the code who did was responsible for setting the error code. return; } nsresult rv; NS_DEFINE_CID(nssComponentCID, NS_NSSCOMPONENT_CID); nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(nssComponentCID, &rv)); if (NS_FAILED(rv)) return; nsXPIDLCString hostName; socketInfo->GetHostName(getter_Copies(hostName)); PRInt32 port; socketInfo->GetPort(&port); - bool suppressMessage = false; - // Try to get a nsISSLErrorListener implementation from the socket consumer. nsCOMPtr<nsIInterfaceRequestor> cb; socketInfo->GetNotificationCallbacks(getter_AddRefs(cb)); if (cb) { nsCOMPtr<nsISSLErrorListener> sel = do_GetInterface(cb); if (sel) { nsIInterfaceRequestor *csi = static_cast<nsIInterfaceRequestor*>(socketInfo); nsCString hostWithPortString = hostName; hostWithPortString.AppendLiteral(":"); hostWithPortString.AppendInt(port); + + bool suppressMessage = false; // obsolete, ignored rv = sel->NotifySSLError(csi, err, hostWithPortString, &suppressMessage); - if (NS_SUCCEEDED(rv) && suppressMessage) - return; } } - if (socketInfo->GetExternalErrorReporting()) { - NS_ConvertASCIItoUTF16 hostNameU(hostName); - nsString formattedString; - (void) getErrorMessage(err, hostNameU, port, nssComponent, formattedString); - socketInfo->SetErrorMessage(formattedString.get()); + socketInfo->SetCanceled(err, PlainErrorMessage); +} + +namespace { + +nsNSSSocketInfo * +getSocketInfoIfRunning(PRFileDesc * fd, + const nsNSSShutDownPreventionLock & /*proofOfLock*/) +{ + if (!fd || !fd->lower || !fd->secret || + fd->identity != nsSSLIOLayerHelpers::nsSSLIOLayerIdentity) { + NS_ERROR("bad file descriptor passed to getSocketInfoIfRunning"); + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return nsnull; } + + nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; + + if (socketInfo->isAlreadyShutDown() || socketInfo->isPK11LoggedOut()) { + PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); + return nsnull; + } + + if (socketInfo->GetErrorCode()) { + PRErrorCode err = socketInfo->GetErrorCode(); + // If we get here, it is probably because cert verification failed and this + // is the first I/O attempt since that failure. + PR_SetError(err, 0); + return nsnull; + } + + return socketInfo; } +} // unnnamed namespace + static PRStatus PR_CALLBACK nsSSLIOLayerConnect(PRFileDesc* fd, const PRNetAddr* addr, PRIntervalTime timeout) { PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] connecting SSL socket\n", (void*)fd)); nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - PRStatus status = PR_SUCCESS; - -#if defined(XP_BEOS) - // Due to BeOS net_server's lack of support for nonblocking sockets, - // we must execute this entire connect as a blocking operation - bug 70217 - - PRSocketOptionData sockopt; - sockopt.option = PR_SockOpt_Nonblocking; - PR_GetSocketOption(fd, &sockopt); - bool oldBlockVal = sockopt.value.non_blocking; - sockopt.option = PR_SockOpt_Nonblocking; - sockopt.value.non_blocking = false; - PR_SetSocketOption(fd, &sockopt); -#endif - - status = fd->lower->methods->connect(fd->lower, addr, -#if defined(XP_BEOS) // bug 70217 - PR_INTERVAL_NO_TIMEOUT); -#else - timeout); -#endif + PRStatus status = fd->lower->methods->connect(fd->lower, addr, timeout); if (status != PR_SUCCESS) { PR_LOG(gPIPNSSLog, PR_LOG_ERROR, ("[%p] Lower layer connect error: %d\n", (void*)fd, PR_GetError())); -#if defined(XP_BEOS) // bug 70217 - goto loser; -#else return status; -#endif } - + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] Connect\n", (void*)fd)); - -#if defined(XP_BEOS) // bug 70217 - loser: - // We put the Nonblocking bit back to the value it was when - // we entered this function. - NS_ASSERTION(sockopt.option == PR_SockOpt_Nonblocking, - "sockopt.option was re-set to an unexpected value"); - sockopt.value.non_blocking = oldBlockVal; - PR_SetSocketOption(fd, &sockopt); -#endif - return status; } // nsPSMRememberCertErrorsTable nsPSMRememberCertErrorsTable::nsPSMRememberCertErrorsTable() { mErrorHosts.Init(16); @@ -1633,28 +1782,34 @@ nsSSLIOLayerClose(PRFileDesc *fd) if (!fd) return PR_FAILURE; PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] Shutting down socket\n", (void*)fd)); nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - return nsSSLThread::requestClose(socketInfo); + return socketInfo->CloseSocketAndDestroy(locker); } -PRStatus nsNSSSocketInfo::CloseSocketAndDestroy() +PRStatus nsNSSSocketInfo::CloseSocketAndDestroy( + const nsNSSShutDownPreventionLock & /*proofOfLock*/) { - nsNSSShutDownPreventionLock locker; - nsNSSShutDownList::trackSSLSocketClose(); PRFileDesc* popped = PR_PopIOLayer(mFd, PR_TOP_IO_LAYER); PRStatus status = mFd->methods->close(mFd); + + // the nsNSSSocketInfo instance can out-live the connection, so we need some + // indication that the connection has been closed. mFd == nsnull is that + // indication. This is needed, for example, when the connection is closed + // before we have finished validating the server's certificate. + mFd = nsnull; + if (status != PR_SUCCESS) return status; popped->identity = PR_INVALID_IO_LAYER; NS_RELEASE_THIS(); popped->dtor(popped); return PR_SUCCESS; } @@ -1782,21 +1937,19 @@ class SSLErrorRunnable : public SyncRunn { nsHandleSSLError(mInfoObject, mErrorCode); } nsRefPtr<nsNSSSocketInfo> mInfoObject; const PRErrorCode mErrorCode; }; -PRInt32 -nsSSLThread::checkHandshake(PRInt32 bytesTransfered, - bool wasReading, - PRFileDesc* ssl_layer_fd, - nsNSSSocketInfo *socketInfo) +PRInt32 checkHandshake(PRInt32 bytesTransfered, bool wasReading, + PRFileDesc* ssl_layer_fd, + nsNSSSocketInfo *socketInfo) { // This is where we work around all of those SSL servers that don't // conform to the SSL spec and shutdown a connection when we request // SSL v3.1 (aka TLS). The spec says the client says what version // of the protocol we're willing to perform, in our case SSL v3.1 // In its response, the server says which version it wants to perform. // Many servers out there only know how to do v3.0. Next, we're supposed // to send back the version of the protocol we requested (ie v3.1). At @@ -1840,19 +1993,27 @@ nsSSLThread::checkHandshake(PRInt32 byte if (!wantRetry // no decision yet && isTLSIntoleranceError(err, socketInfo->GetHasCleartextPhase())) { wantRetry = nsSSLIOLayerHelpers::rememberPossibleTLSProblemSite(ssl_layer_fd, socketInfo); } } - // This is the common place where we trigger an error message on a SSL socket. - // This might be reached at any time of the connection. - if (!wantRetry && (IS_SSL_ERROR(err) || IS_SEC_ERROR(err))) { + // This is the common place where we trigger non-cert-errors on a SSL + // socket. This might be reached at any time of the connection. + // + // The socketInfo->GetErrorCode() check is here to ensure we don't try to + // do the synchronous dispatch to the main thread unnecessarily after we've + // already handled a certificate error. (SSLErrorRunnable calls + // nsHandleSSLError, which has logic to avoid replacing the error message, + // so without the !socketInfo->GetErrorCode(), it would just be an + // expensive no-op.) + if (!wantRetry && (IS_SSL_ERROR(err) || IS_SEC_ERROR(err)) && + !socketInfo->GetErrorCode()) { nsRefPtr<SyncRunnableBase> runnable = new SSLErrorRunnable(socketInfo, err); (void) runnable->DispatchToMainThreadAndWait(); } } else if (wasReading && 0 == bytesTransfered) // zero bytes on reading, socket closed { if (handleHandshakeResultNow) @@ -1880,54 +2041,79 @@ nsSSLThread::checkHandshake(PRInt32 byte socketInfo->SetHandshakePending(false); socketInfo->SetHandshakeInProgress(false); } return bytesTransfered; } static PRInt16 PR_CALLBACK -nsSSLIOLayerPoll(PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags) +nsSSLIOLayerPoll(PRFileDesc * fd, PRInt16 in_flags, PRInt16 *out_flags) { - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] polling SSL socket\n", (void*)fd)); nsNSSShutDownPreventionLock locker; if (!out_flags) { NS_WARNING("nsSSLIOLayerPoll called with null out_flags"); return 0; } *out_flags = 0; - if (!fd) - { - NS_WARNING("nsSSLIOLayerPoll called with null fd"); - return 0; + nsNSSSocketInfo * socketInfo = getSocketInfoIfRunning(fd, locker); + if (!socketInfo) { + // If we get here, it is probably because certificate validation failed + // and this is the first I/O operation after the failure. + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("[%p] polling SSL socket right after certificate verification failed " + "or NSS shutdown or SDR logout %d\n", + fd, (int) in_flags)); + + NS_ASSERTION(in_flags & PR_POLL_EXCEPT, + "caller did not poll for EXCEPT (canceled)"); + // Since this poll method cannot return errors, we want the caller to call + // PR_Send/PR_Recv right away to get the error, so we tell that we are + // ready for whatever I/O they are asking for. (See getSocketInfoIfRunning). + *out_flags = in_flags | PR_POLL_EXCEPT; // see also bug 480619 + return in_flags; } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestPoll(socketInfo, in_flags, out_flags); + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + (socketInfo->IsWaitingForCertVerification() + ? "[%p] polling SSL socket during certificate verification using lower %d\n" + : "[%p] poll SSL socket using lower %d\n", + fd, (int) in_flags)); + + if (socketInfo->HandshakeTimeout()) { + NS_ASSERTION(in_flags & PR_POLL_EXCEPT, + "caller did not poll for EXCEPT (handshake timeout)"); + *out_flags = in_flags | PR_POLL_EXCEPT; + return in_flags; + } + + // We want the handshake to continue during certificate validation, so we + // don't need to do anything special here. libssl automatically blocks when + // it reaches any point that would be unsafe to send/receive something before + // cert validation is complete. + PRInt16 result = fd->lower->methods->poll(fd->lower, in_flags, out_flags); + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] poll SSL socket returned %d\n", + (void*)fd, (int) result)); + return result; } bool nsSSLIOLayerHelpers::nsSSLIOLayerInitialized = false; PRDescIdentity nsSSLIOLayerHelpers::nsSSLIOLayerIdentity; PRIOMethods nsSSLIOLayerHelpers::nsSSLIOLayerMethods; Mutex *nsSSLIOLayerHelpers::mutex = nsnull; nsCStringHashSet *nsSSLIOLayerHelpers::mTLSIntolerantSites = nsnull; nsCStringHashSet *nsSSLIOLayerHelpers::mTLSTolerantSites = nsnull; nsPSMRememberCertErrorsTable *nsSSLIOLayerHelpers::mHostsWithCertErrors = nsnull; nsCStringHashSet *nsSSLIOLayerHelpers::mRenegoUnrestrictedSites = nsnull; bool nsSSLIOLayerHelpers::mTreatUnsafeNegotiationAsBroken = false; PRInt32 nsSSLIOLayerHelpers::mWarnLevelMissingRFC5746 = 1; -PRFileDesc *nsSSLIOLayerHelpers::mSharedPollableEvent = nsnull; -nsNSSSocketInfo *nsSSLIOLayerHelpers::mSocketOwningPollableEvent = nsnull; -bool nsSSLIOLayerHelpers::mPollableEventCurrentlySet = false; static PRIntn _PSM_InvalidInt(void) { PR_ASSERT(!"I/O method is invalid"); PR_SetError(PR_INVALID_METHOD_ERROR, 0); return -1; } @@ -1950,109 +2136,100 @@ static PRFileDesc *_PSM_InvalidDesc(void PR_ASSERT(!"I/O method is invalid"); PR_SetError(PR_INVALID_METHOD_ERROR, 0); return NULL; } static PRStatus PR_CALLBACK PSMGetsockname(PRFileDesc *fd, PRNetAddr *addr) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - } - - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestGetsockname(socketInfo, addr); + + return fd->lower->methods->getsockname(fd->lower, addr); } static PRStatus PR_CALLBACK PSMGetpeername(PRFileDesc *fd, PRNetAddr *addr) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - } - - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestGetpeername(socketInfo, addr); + + return fd->lower->methods->getpeername(fd->lower, addr); } static PRStatus PR_CALLBACK PSMGetsocketoption(PRFileDesc *fd, PRSocketOptionData *data) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - } - - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestGetsocketoption(socketInfo, data); + + return fd->lower->methods->getsocketoption(fd, data); } static PRStatus PR_CALLBACK PSMSetsocketoption(PRFileDesc *fd, const PRSocketOptionData *data) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - } - - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestSetsocketoption(socketInfo, data); + + return fd->lower->methods->setsocketoption(fd, data); } static PRInt32 PR_CALLBACK PSMRecv(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { - PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + nsNSSSocketInfo *socketInfo = getSocketInfoIfRunning(fd, locker); + if (!socketInfo) return -1; - } - - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - if (flags == PR_MSG_PEEK) { - return nsSSLThread::requestRecvMsgPeek(socketInfo, buf, amount, flags, timeout); - } - - if (flags != 0) { + + if (flags != PR_MSG_PEEK && flags != 0) { PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); return -1; } - return nsSSLThread::requestRead(socketInfo, buf, amount, timeout); + PRInt32 bytesRead = fd->lower->methods->recv(fd->lower, buf, amount, flags, + timeout); + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] read %d bytes\n", (void*)fd, bytesRead)); + +#ifdef DEBUG_SSL_VERBOSE + DEBUG_DUMP_BUFFER((unsigned char*)buf, bytesRead); +#endif + + return checkHandshake(bytesRead, true, fd, socketInfo); } static PRInt32 PR_CALLBACK PSMSend(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { - PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + nsNSSSocketInfo *socketInfo = getSocketInfoIfRunning(fd, locker); + if (!socketInfo) return -1; - } if (flags != 0) { PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); return -1; } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestWrite(socketInfo, buf, amount, timeout); +#ifdef DEBUG_SSL_VERBOSE + DEBUG_DUMP_BUFFER((unsigned char*)buf, amount); +#endif + + PRInt32 bytesWritten = fd->lower->methods->send(fd->lower, buf, amount, + flags, timeout); + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes\n", + fd, bytesWritten)); + + return checkHandshake(bytesWritten, false, fd, socketInfo); } static PRInt32 PR_CALLBACK nsSSLIOLayerRead(PRFileDesc* fd, void* buf, PRInt32 amount) { return PSMRecv(fd, buf, amount, 0, PR_INTERVAL_NO_TIMEOUT); } @@ -2060,24 +2237,21 @@ static PRInt32 PR_CALLBACK nsSSLIOLayerWrite(PRFileDesc* fd, const void* buf, PRInt32 amount) { return PSMSend(fd, buf, amount, 0, PR_INTERVAL_NO_TIMEOUT); } static PRStatus PR_CALLBACK PSMConnectcontinue(PRFileDesc *fd, PRInt16 out_flags) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) { return PR_FAILURE; } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestConnectcontinue(socketInfo, out_flags); + return fd->lower->methods->connectcontinue(fd, out_flags); } nsresult nsSSLIOLayerHelpers::Init() { if (!nsSSLIOLayerInitialized) { nsSSLIOLayerInitialized = true; nsSSLIOLayerIdentity = PR_GetUniqueIdentity("NSS layer"); nsSSLIOLayerMethods = *PR_GetDefaultIOMethods(); @@ -2112,20 +2286,16 @@ nsresult nsSSLIOLayerHelpers::Init() nsSSLIOLayerMethods.close = nsSSLIOLayerClose; nsSSLIOLayerMethods.write = nsSSLIOLayerWrite; nsSSLIOLayerMethods.read = nsSSLIOLayerRead; nsSSLIOLayerMethods.poll = nsSSLIOLayerPoll; } mutex = new Mutex("nsSSLIOLayerHelpers.mutex"); - mSharedPollableEvent = PR_NewPollableEvent(); - - // if we can not get a pollable event, we'll have to do busy waiting - mTLSIntolerantSites = new nsCStringHashSet(); if (!mTLSIntolerantSites) return NS_ERROR_OUT_OF_MEMORY; mTLSIntolerantSites->Init(1); mTLSTolerantSites = new nsCStringHashSet(); if (!mTLSTolerantSites) @@ -2717,32 +2887,32 @@ class ClientAuthDataRunnable : public Sy public: ClientAuthDataRunnable(CERTDistNames* caNames, CERTCertificate** pRetCert, SECKEYPrivateKey** pRetKey, nsNSSSocketInfo * info, CERTCertificate * serverCert) : mRV(SECFailure) , mErrorCodeToReport(SEC_ERROR_NO_MEMORY) - , mCANames(caNames) , mPRetCert(pRetCert) , mPRetKey(pRetKey) + , mCANames(caNames) , mSocketInfo(info) , mServerCert(serverCert) { } SECStatus mRV; // out PRErrorCode mErrorCodeToReport; // out + CERTCertificate** const mPRetCert; // in/out + SECKEYPrivateKey** const mPRetKey; // in/out protected: virtual void RunOnTargetThread(); private: CERTDistNames* const mCANames; // in - CERTCertificate** const mPRetCert; // in/out - SECKEYPrivateKey** const mPRetKey; // in/out nsNSSSocketInfo * const mSocketInfo; // in CERTCertificate * const mServerCert; // in }; /* * Function: SECStatus SSM_SSLGetClientAuthData() * Purpose: this callback function is used to pull client certificate * information upon server request @@ -2775,27 +2945,42 @@ SECStatus nsNSS_SSLGetClientAuthData(voi CERTCertificate* serverCert = SSL_PeerCertificate(socket); if (!serverCert) { NS_NOTREACHED("Missing server certificate should have been detected during " "server cert authentication."); PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0); return SECFailure; } + if (info->GetJoined()) { + // We refuse to send a client certificate when there are multiple hostnames + // joined on this connection, because we only show the user one hostname + // (mHostName) in the client certificate UI. + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("[%p] Not returning client cert due to previous join\n", socket)); + *pRetCert = nsnull; + *pRetKey = nsnull; + return SECSuccess; + } + // XXX: This should be done asynchronously; see bug 696976 nsRefPtr<ClientAuthDataRunnable> runnable = new ClientAuthDataRunnable(caNames, pRetCert, pRetKey, info, serverCert); nsresult rv = runnable->DispatchToMainThreadAndWait(); if (NS_FAILED(rv)) { PR_SetError(SEC_ERROR_NO_MEMORY, 0); return SECFailure; } if (runnable->mRV != SECSuccess) { - PORT_SetError(runnable->mErrorCodeToReport); + PR_SetError(runnable->mErrorCodeToReport, 0); + } else if (*runnable->mPRetCert || *runnable->mPRetKey) { + // Make joinConnection prohibit joining after we've sent a client cert + info->SetSentClientCert(); } return runnable->mRV; } void ClientAuthDataRunnable::RunOnTargetThread() { PRArenaPool* arena = NULL; @@ -3215,390 +3400,408 @@ done: } class CertErrorRunnable : public SyncRunnableBase { public: CertErrorRunnable(const void * fdForLogging, nsIX509Cert * cert, nsNSSSocketInfo * infoObject, - const CERTVerifyLog * verify_log, - bool hasCertNameMismatch, - PRErrorCode defaultErrorCodeToReport) + PRErrorCode defaultErrorCodeToReport, + PRUint32 collectedErrors, + PRErrorCode errorCodeTrust, + PRErrorCode errorCodeMismatch, + PRErrorCode errorCodeExpired) : mFdForLogging(fdForLogging), mCert(cert), mInfoObject(infoObject), - mVerifyLog(verify_log), mHasCertNameMismatch(hasCertNameMismatch), - mRv(SECFailure), mErrorCodeToReport(defaultErrorCodeToReport) + mDefaultErrorCodeToReport(defaultErrorCodeToReport), + mCollectedErrors(collectedErrors), + mErrorCodeTrust(errorCodeTrust), + mErrorCodeMismatch(errorCodeMismatch), + mErrorCodeExpired(errorCodeExpired) { } + NS_DECL_NSIRUNNABLE virtual void RunOnTargetThread(); + nsCOMPtr<nsIRunnable> mResult; // out +private: + SSLServerCertVerificationResult* CheckCertOverrides(); - // in - const void * const mFdForLogging; - nsCOMPtr<nsIX509Cert> mCert; - nsNSSSocketInfo * const mInfoObject; - const CERTVerifyLog * const mVerifyLog; - const bool mHasCertNameMismatch; - nsXPIDLCString mHostname; - - SECStatus mRv; // out - PRErrorCode mErrorCodeToReport; // in/out + const void * const mFdForLogging; // may become an invalid pointer; do not dereference + const nsCOMPtr<nsIX509Cert> mCert; + const nsRefPtr<nsNSSSocketInfo> mInfoObject; + const PRErrorCode mDefaultErrorCodeToReport; + const PRUint32 mCollectedErrors; + const PRErrorCode mErrorCodeTrust; + const PRErrorCode mErrorCodeMismatch; + const PRErrorCode mErrorCodeExpired; }; -static SECStatus -cancel_and_failure(nsNSSSocketInfo* infoObject) -{ - infoObject->SetCanceled(true); - return SECFailure; -} - -static SECStatus -nsNSSBadCertHandler(void *arg, PRFileDesc *sslSocket) +namespace mozilla { namespace psm { + +// Returns SECSuccess if it dispatched the CertErrorRunnable. In that case, +// the caller should NOT dispatch its own SSLServerCertVerificationResult; +// the CertErrorRunnable will do it instead. +// +// Returns SECFailure with the error code set if it does not dispatch the +// CertErrorRunnable. In that case, the caller should dispatch its own +// SSLServerCertVerificationResult with the error code from PR_GetError(). +SECStatus +HandleBadCertificate(PRErrorCode defaultErrorCodeToReport, + nsNSSSocketInfo * socketInfo, CERTCertificate & cert, + const void * fdForLogging, + const nsNSSShutDownPreventionLock & /*proofOfLock*/) { // cert was revoked, don't do anything else - // Calling cancel_and_failure is not necessary, and would be wrong, - // [for errors other than the ones explicitly handled below,] - // because it suppresses error reporting. - PRErrorCode defaultErrorCodeToReport = PR_GetError(); - if (defaultErrorCodeToReport == SEC_ERROR_REVOKED_CERTIFICATE) + if (defaultErrorCodeToReport == SEC_ERROR_REVOKED_CERTIFICATE) { + PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0); return SECFailure; + } if (defaultErrorCodeToReport == 0) { NS_ERROR("No error code set during certificate validation failure."); - defaultErrorCodeToReport = SEC_ERROR_CERT_NOT_VALID; - } - - nsNSSShutDownPreventionLock locker; - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo *)arg; - if (!infoObject) + PR_SetError(PR_INVALID_STATE_ERROR, 0); return SECFailure; - - if (nsSSLThread::stoppedOrStopping()) - return cancel_and_failure(infoObject); - - CERTCertificate *peerCert = nsnull; - CERTCertificateCleaner peerCertCleaner(peerCert); - peerCert = SSL_PeerCertificate(sslSocket); - if (!peerCert) - return cancel_and_failure(infoObject); + } nsRefPtr<nsNSSCertificate> nssCert; - nssCert = nsNSSCertificate::Create(peerCert); - if (!nssCert) - return cancel_and_failure(infoObject); + nssCert = nsNSSCertificate::Create(&cert); + if (!nssCert) { + NS_ERROR("nsNSSCertificate::Create failed in DispatchCertErrorRunnable"); + PR_SetError(SEC_ERROR_NO_MEMORY, 0); + return SECFailure; + } SECStatus srv; nsresult nsrv; nsCOMPtr<nsINSSComponent> inss = do_GetService(kNSSComponentCID, &nsrv); - if (!inss) - return cancel_and_failure(infoObject); + if (!inss) { + NS_ERROR("do_GetService(kNSSComponentCID) failed in DispatchCertErrorRunnable"); + PR_SetError(defaultErrorCodeToReport, 0); + return SECFailure; + } + nsRefPtr<nsCERTValInParamWrapper> survivingParams; nsrv = inss->GetDefaultCERTValInParam(survivingParams); - if (NS_FAILED(nsrv)) - return cancel_and_failure(infoObject); + if (NS_FAILED(nsrv)) { + NS_ERROR("GetDefaultCERTValInParam failed in DispatchCertErrorRunnable"); + PR_SetError(defaultErrorCodeToReport, 0); + return SECFailure; + } - char *hostname = SSL_RevealURL(sslSocket); - if (!hostname) - return cancel_and_failure(infoObject); - - charCleaner hostnameCleaner(hostname); + PRArenaPool *log_arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + PRArenaPoolCleanerFalseParam log_arena_cleaner(log_arena); + if (!log_arena) { + NS_ERROR("PORT_NewArena failed in DispatchCertErrorRunnable"); + return SECFailure; // PORT_NewArena set error code + } + + CERTVerifyLog *verify_log = PORT_ArenaZNew(log_arena, CERTVerifyLog); + if (!verify_log) { + NS_ERROR("PORT_ArenaZNew failed in DispatchCertErrorRunnable"); + return SECFailure; // PORT_ArenaZNew set error code + } + CERTVerifyLogContentsCleaner verify_log_cleaner(verify_log); + verify_log->arena = log_arena; + + if (!nsNSSComponent::globalConstFlagUsePKIXVerification) { + srv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), &cert, + true, certificateUsageSSLServer, + PR_Now(), static_cast<void*>(socketInfo), + verify_log, NULL); + } + else { + CERTValOutParam cvout[2]; + cvout[0].type = cert_po_errorLog; + cvout[0].value.pointer.log = verify_log; + cvout[1].type = cert_po_end; + + srv = CERT_PKIXVerifyCert(&cert, certificateUsageSSLServer, + survivingParams->GetRawPointerForNSS(), + cvout, static_cast<void*>(socketInfo)); + } + + // We ignore the result code of the cert verification. + // Either it is a failure, which is expected, and we'll process the + // verify log below. + // Or it is a success, then a domain mismatch is the only + // possible failure. + + PRErrorCode errorCodeMismatch = 0; + PRErrorCode errorCodeTrust = 0; + PRErrorCode errorCodeExpired = 0; + + PRUint32 collected_errors = 0; + + if (socketInfo->IsCertIssuerBlacklisted()) { + collected_errors |= nsICertOverrideService::ERROR_UNTRUSTED; + errorCodeTrust = defaultErrorCodeToReport; + } // Check the name field against the desired hostname. - bool hasCertNameMismatch = - hostname[0] && CERT_VerifyCertName(peerCert, hostname) != SECSuccess; - + if (CERT_VerifyCertName(&cert, socketInfo->GetHostName()) != SECSuccess) { + collected_errors |= nsICertOverrideService::ERROR_MISMATCH; + errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN; + } + + CERTVerifyLogNode *i_node; + for (i_node = verify_log->head; i_node; i_node = i_node->next) { - PRArenaPool *log_arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - if (!log_arena) - return cancel_and_failure(infoObject); - - PRArenaPoolCleanerFalseParam log_arena_cleaner(log_arena); - - CERTVerifyLog *verify_log = PORT_ArenaZNew(log_arena, CERTVerifyLog); - if (!verify_log) - return cancel_and_failure(infoObject); - - CERTVerifyLogContentsCleaner verify_log_cleaner(verify_log); - - verify_log->arena = log_arena; - - if (!nsNSSComponent::globalConstFlagUsePKIXVerification) { - srv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), peerCert, - true, certificateUsageSSLServer, - PR_Now(), (void*)infoObject, - verify_log, NULL); - } - else { - CERTValOutParam cvout[2]; - cvout[0].type = cert_po_errorLog; - cvout[0].value.pointer.log = verify_log; - cvout[1].type = cert_po_end; - - srv = CERT_PKIXVerifyCert(peerCert, certificateUsageSSLServer, - survivingParams->GetRawPointerForNSS(), - cvout, (void*)infoObject); + switch (i_node->error) + { + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_CA_CERT_INVALID: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + case SEC_ERROR_UNTRUSTED_CERT: + case SEC_ERROR_INADEQUATE_KEY_USAGE: + // We group all these errors as "cert not trusted" + collected_errors |= nsICertOverrideService::ERROR_UNTRUSTED; + if (errorCodeTrust == SECSuccess) { + errorCodeTrust = i_node->error; + } + break; + case SSL_ERROR_BAD_CERT_DOMAIN: + collected_errors |= nsICertOverrideService::ERROR_MISMATCH; + if (errorCodeMismatch == SECSuccess) { + errorCodeMismatch = i_node->error; + } + break; + case SEC_ERROR_EXPIRED_CERTIFICATE: + collected_errors |= nsICertOverrideService::ERROR_TIME; + if (errorCodeExpired == SECSuccess) { + errorCodeExpired = i_node->error; + } + break; + default: + PR_SetError(i_node->error, 0); + return SECFailure; } - - // We ignore the result code of the cert verification. - // Either it is a failure, which is expected, and we'll process the - // verify log below. - // Or it is a success, then a domain mismatch is the only - // possible failure. - - nsRefPtr<CertErrorRunnable> runnable = - new CertErrorRunnable(static_cast<void*>(sslSocket), - static_cast<nsIX509Cert*>(nssCert.get()), - infoObject, verify_log, hasCertNameMismatch, - defaultErrorCodeToReport); - - // now grab the host name to pass to the STS Service - nsrv = infoObject->GetHostName(getter_Copies(runnable->mHostname)); - if (NS_FAILED(nsrv)) { - PR_SetError(defaultErrorCodeToReport, 0); - return SECFailure; - } - - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("[%p][%p] Before dispatching CertErrorRunnable\n", - sslSocket, runnable.get())); - - // Dispatch SYNC since the result is used below - (void) runnable->DispatchToMainThreadAndWait(); - - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("[%p][%p] After dispatching CertErrorRunnable\n", - sslSocket, runnable.get())); - - if (runnable->mRv == SECSuccess) - return SECSuccess; - - NS_ASSERTION(runnable->mErrorCodeToReport != 0, - "CertErrorRunnable did not set error code."); - PR_SetError(runnable->mErrorCodeToReport ? runnable->mErrorCodeToReport - : defaultErrorCodeToReport, 0); + } + + if (!collected_errors) + { + // This will happen when CERT_*Verify* only returned error(s) that are + // not on our whitelist of overridable certificate errors. + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] !collected_errors: %d\n", + fdForLogging, static_cast<int>(defaultErrorCodeToReport))); + PR_SetError(defaultErrorCodeToReport, 0); return SECFailure; } + + socketInfo->SetStatusErrorBits(*nssCert, collected_errors); + + nsRefPtr<CertErrorRunnable> runnable = + new CertErrorRunnable(fdForLogging, + static_cast<nsIX509Cert*>(nssCert.get()), + socketInfo, defaultErrorCodeToReport, + collected_errors, errorCodeTrust, + errorCodeMismatch, errorCodeExpired); + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("[%p][%p] Before dispatching CertErrorRunnable\n", + fdForLogging, runnable.get())); + + nsresult nrv; + nsCOMPtr<nsIEventTarget> stsTarget + = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv); + if (NS_SUCCEEDED(nrv)) { + nrv = stsTarget->Dispatch(runnable, NS_DISPATCH_NORMAL); + } + if (NS_FAILED(nrv)) { + NS_ERROR("Failed to dispatch CertErrorRunnable"); + PR_SetError(defaultErrorCodeToReport, 0); + return SECFailure; + } + + return SECSuccess; } -void CertErrorRunnable::RunOnTargetThread() +} } // namespace mozilla::psm + +void +nsNSSSocketInfo::SetStatusErrorBits(nsIX509Cert & cert, + PRUint32 collected_errors) +{ + MutexAutoLock lock(mMutex); + + if (!mSSLStatus) + mSSLStatus = new nsSSLStatus(); + + mSSLStatus->mServerCert = &cert; + + mSSLStatus->mHaveCertErrorBits = true; + mSSLStatus->mIsDomainMismatch = + collected_errors & nsICertOverrideService::ERROR_MISMATCH; + mSSLStatus->mIsNotValidAtThisTime = + collected_errors & nsICertOverrideService::ERROR_TIME; + mSSLStatus->mIsUntrusted = + collected_errors & nsICertOverrideService::ERROR_UNTRUSTED; + + nsSSLIOLayerHelpers::mHostsWithCertErrors->RememberCertHasError( + this, mSSLStatus, SECFailure); +} + +SSLServerCertVerificationResult * +CertErrorRunnable::CheckCertOverrides() { PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] top of CertErrorRunnable::Run\n", mFdForLogging, this)); if (!NS_IsMainThread()) { - NS_ERROR("CertErrorRunnable::RunOnTargetThread called off main thread"); - return; - } - - if (nsSSLThread::stoppedOrStopping()) - return; - - PRErrorCode errorCodeMismatch = 0; - PRErrorCode errorCodeTrust = 0; - PRErrorCode errorCodeExpired = 0; - - PRUint32 collected_errors = 0; - - if (mInfoObject->IsCertIssuerBlacklisted()) { - collected_errors |= nsICertOverrideService::ERROR_UNTRUSTED; - errorCodeTrust = mErrorCodeToReport; - } - - if (mHasCertNameMismatch) { - collected_errors |= nsICertOverrideService::ERROR_MISMATCH; - errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN; + NS_ERROR("CertErrorRunnable::CheckCertOverrides called off main thread"); + return new SSLServerCertVerificationResult(*mInfoObject, + mDefaultErrorCodeToReport); } - { - CERTVerifyLogNode *i_node; - for (i_node = mVerifyLog->head; i_node; i_node = i_node->next) - { - switch (i_node->error) - { - case SEC_ERROR_UNKNOWN_ISSUER: - case SEC_ERROR_CA_CERT_INVALID: - case SEC_ERROR_UNTRUSTED_ISSUER: - case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: - case SEC_ERROR_UNTRUSTED_CERT: - case SEC_ERROR_INADEQUATE_KEY_USAGE: - // We group all these errors as "cert not trusted" - collected_errors |= nsICertOverrideService::ERROR_UNTRUSTED; - if (errorCodeTrust == SECSuccess) { - errorCodeTrust = i_node->error; - } - break; - case SSL_ERROR_BAD_CERT_DOMAIN: - collected_errors |= nsICertOverrideService::ERROR_MISMATCH; - if (errorCodeMismatch == SECSuccess) { - errorCodeMismatch = i_node->error; - } - break; - case SEC_ERROR_EXPIRED_CERTIFICATE: - collected_errors |= nsICertOverrideService::ERROR_TIME; - if (errorCodeExpired == SECSuccess) { - errorCodeExpired = i_node->error; - } - break; - default: - // we are not willing to continue on any other error - nsHandleSSLError(mInfoObject, i_node->error); - // this error is our stop condition, so let's make sure - // this error code will be reported to the external world. - mErrorCodeToReport = i_node->error; - mInfoObject->SetCanceled(true); - return; - } - } - } - - if (!collected_errors) - { - NS_NOTREACHED("why did NSS call our bad cert handler if all looks good? Let's cancel the connection"); - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] !collected_errors\n", - mFdForLogging, this)); - return; - } - - nsRefPtr<nsSSLStatus> status = mInfoObject->SSLStatus(); - if (!status) { - status = new nsSSLStatus(); - mInfoObject->SetSSLStatus(status); - } - - if (status) { - if (!status->mServerCert) { - status->mServerCert = mCert; - } - - status->mHaveCertErrorBits = true; - status->mIsDomainMismatch = collected_errors & nsICertOverrideService::ERROR_MISMATCH; - status->mIsNotValidAtThisTime = collected_errors & nsICertOverrideService::ERROR_TIME; - status->mIsUntrusted = collected_errors & nsICertOverrideService::ERROR_UNTRUSTED; - - nsSSLIOLayerHelpers::mHostsWithCertErrors->RememberCertHasError( - mInfoObject, status, SECFailure); - } - - nsDependentCString hostString(mHostname); - PRInt32 port; mInfoObject->GetPort(&port); - nsCString hostWithPortString = hostString; + nsCString hostWithPortString; + hostWithPortString.AppendASCII(mInfoObject->GetHostName()); hostWithPortString.AppendLiteral(":"); hostWithPortString.AppendInt(port); - NS_ConvertUTF8toUTF16 hostWithPortStringUTF16(hostWithPortString); - - PRUint32 remaining_display_errors = collected_errors; + PRUint32 remaining_display_errors = mCollectedErrors; nsresult nsrv; // Enforce Strict-Transport-Security for hosts that are "STS" hosts: // connections must be dropped when there are any certificate errors // (STS Spec section 7.3). bool strictTransportSecurityEnabled = false; nsCOMPtr<nsIStrictTransportSecurityService> stss = do_GetService(NS_STSSERVICE_CONTRACTID, &nsrv); if (NS_SUCCEEDED(nsrv)) { - nsrv = stss->IsStsHost(mHostname, &strictTransportSecurityEnabled); + nsrv = stss->IsStsHost(mInfoObject->GetHostName(), + &strictTransportSecurityEnabled); } - if (NS_FAILED(nsrv)) - return; // use default rv and errorCodeToReport + if (NS_FAILED(nsrv)) { + return new SSLServerCertVerificationResult(*mInfoObject, + mDefaultErrorCodeToReport); + } if (!strictTransportSecurityEnabled) { nsCOMPtr<nsICertOverrideService> overrideService = do_GetService(NS_CERTOVERRIDE_CONTRACTID); // it is fine to continue without the nsICertOverrideService PRUint32 overrideBits = 0; if (overrideService) { bool haveOverride; bool isTemporaryOverride; // we don't care - + nsCString hostString(mInfoObject->GetHostName()); nsrv = overrideService->HasMatchingOverride(hostString, port, mCert, &overrideBits, &isTemporaryOverride, &haveOverride); if (NS_SUCCEEDED(nsrv) && haveOverride) { - // remove the errors that are already overriden + // remove the errors that are already overriden remaining_display_errors -= overrideBits; } } if (!remaining_display_errors) { // all errors are covered by override rules, so let's accept the cert PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] All errors covered by override rules\n", mFdForLogging, this)); - mRv = SECSuccess; - return; + return new SSLServerCertVerificationResult(*mInfoObject, 0); } } else { PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] Strict-Transport-Security is violated: untrusted " "transport layer\n", mFdForLogging, this)); } PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] Certificate error was not overridden\n", mFdForLogging, this)); // Ok, this is a full stop. - // First, deliver the technical details of the broken SSL status, - // giving the caller a chance to suppress the error messages. - - bool suppressMessage = false; + // First, deliver the technical details of the broken SSL status. // Try to get a nsIBadCertListener2 implementation from the socket consumer. nsCOMPtr<nsIInterfaceRequestor> cb; mInfoObject->GetNotificationCallbacks(getter_AddRefs(cb)); if (cb) { nsCOMPtr<nsIBadCertListener2> bcl = do_GetInterface(cb); if (bcl) { nsIInterfaceRequestor *csi = static_cast<nsIInterfaceRequestor*>(mInfoObject); - nsrv = bcl->NotifyCertProblem(csi, status, hostWithPortString, &suppressMessage); + bool suppressMessage = false; // obsolete, ignored + nsrv = bcl->NotifyCertProblem(csi, mInfoObject->SSLStatus(), + hostWithPortString, &suppressMessage); } } nsCOMPtr<nsIRecentBadCertsService> recentBadCertsService = do_GetService(NS_RECENTBADCERTS_CONTRACTID); - + if (recentBadCertsService) { - recentBadCertsService->AddBadCert(hostWithPortStringUTF16, status); + NS_ConvertUTF8toUTF16 hostWithPortStringUTF16(hostWithPortString); + recentBadCertsService->AddBadCert(hostWithPortStringUTF16, + mInfoObject->SSLStatus()); } // pick the error code to report by priority - mErrorCodeToReport = 0; - if (remaining_display_errors & nsICertOverrideService::ERROR_UNTRUSTED) - mErrorCodeToReport = errorCodeTrust; - else if (remaining_display_errors & nsICertOverrideService::ERROR_MISMATCH) - mErrorCodeToReport = errorCodeMismatch; - else if (remaining_display_errors & nsICertOverrideService::ERROR_TIME) - mErrorCodeToReport = errorCodeExpired; - - if (!suppressMessage && mInfoObject->GetExternalErrorReporting()) { - NS_ConvertASCIItoUTF16 hostU(hostString); - NS_ConvertASCIItoUTF16 hostWithPortU(hostWithPortString); - nsString formattedString; - getInvalidCertErrorMessage(remaining_display_errors, mErrorCodeToReport, - errorCodeTrust, errorCodeMismatch, - errorCodeExpired, hostU, hostWithPortU, port, - mCert, formattedString); - mInfoObject->SetErrorMessage(formattedString.get()); + PRErrorCode errorCodeToReport = mErrorCodeTrust ? mErrorCodeTrust + : mErrorCodeMismatch ? mErrorCodeMismatch + : mErrorCodeExpired ? mErrorCodeExpired + : mDefaultErrorCodeToReport; + + return new SSLServerCertVerificationResult(*mInfoObject, errorCodeToReport, + OverridableCertErrorMessage); +} + +NS_IMETHODIMP +CertErrorRunnable::Run() +{ + // This code is confusing: First, Run() is called on the socket transport + // thread. Then we re-dispatch it to the main thread synchronously (step 1). + // On the main thread, we call CheckCertOverrides (step 2). Then we return + // from the main thread and are back on the socket transport thread. There, + // we run the result runnable directly (step 3). + if (!NS_IsMainThread()) { + // We are running on the socket transport thread. We need to re-dispatch + // ourselves synchronously to the main thread. + DispatchToMainThreadAndWait(); // step 1 + + // step 3 + if (!mResult) { + // Either the dispatch failed or CheckCertOverrides wrongly returned null + NS_ERROR("Did not create a SSLServerCertVerificationResult"); + mResult = new SSLServerCertVerificationResult(*mInfoObject, + PR_INVALID_STATE_ERROR); + } + return mResult->Run(); + } else { + // block this thread (the socket transport thread) until RunOnTargetThread + // is complete. + return SyncRunnableBase::Run(); // step 2 } - - mInfoObject->SetCanceled(true); } +void +CertErrorRunnable::RunOnTargetThread() +{ + // Now we are running on the main thread, blocking the socket tranposrt + // thread. This is exactly the state we need to be in to call + // CheckCertOverrides; CheckCertOverrides must block events on both of + // these threads because it calls nsNSSSocketInfo::GetInterface(), + // which calls nsHttpConnection::GetInterface() through + // nsNSSSocketInfo::mCallbacks. nsHttpConnection::GetInterface must always + // execute on the main thread, with the socket transport thread blocked. + mResult = CheckCertOverrides(); +} + static PRFileDesc* nsSSLIOLayerImportFD(PRFileDesc *fd, nsNSSSocketInfo *infoObject, const char *host, bool anonymousLoad) { nsNSSShutDownPreventionLock locker; PRFileDesc* sslSock = SSL_ImportFD(nsnull, fd); @@ -3612,21 +3815,24 @@ nsSSLIOLayerImportFD(PRFileDesc *fd, // Disable this hook if we connect anonymously. See bug 466080. if (anonymousLoad) { SSL_GetClientAuthDataHook(sslSock, NULL, infoObject); } else { SSL_GetClientAuthDataHook(sslSock, (SSLGetClientAuthData)nsNSS_SSLGetClientAuthData, infoObject); } - SSL_AuthCertificateHook(sslSock, AuthCertificateCallback, 0); - - PRInt32 ret = SSL_SetURL(sslSock, host); - if (ret == -1) { - NS_ASSERTION(false, "NSS: Error setting server name"); + if (SECSuccess != SSL_AuthCertificateHook(sslSock, AuthCertificateHook, + infoObject)) { + NS_NOTREACHED("failed to configure AuthCertificateHook"); + goto loser; + } + + if (SECSuccess != SSL_SetURL(sslSock, host)) { + NS_NOTREACHED("SSL_SetURL failed"); goto loser; } return sslSock; loser: if (sslSock) { PR_Close(sslSock); } return nsnull; @@ -3662,20 +3868,16 @@ nsSSLIOLayerSetOptions(PRFileDesc *fd, b // One advantage of this approach, if a site only supports the older // hellos, it is more likely that we will get a reasonable error code // on our single retry attempt. } if (SECSuccess != SSL_OptionSet(fd, SSL_HANDSHAKE_AS_CLIENT, true)) { return NS_ERROR_FAILURE; } - if (SECSuccess != SSL_BadCertHook(fd, (SSLBadCertHandler) nsNSSBadCertHandler, - infoObject)) { - return NS_ERROR_FAILURE; - } if (nsSSLIOLayerHelpers::isRenegoUnrestrictedSite(nsDependentCString(host))) { if (SECSuccess != SSL_OptionSet(fd, SSL_REQUIRE_SAFE_NEGOTIATION, false)) { return NS_ERROR_FAILURE; } if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_UNRESTRICTED)) { return NS_ERROR_FAILURE; }
--- a/security/manager/ssl/src/nsNSSIOLayer.h +++ b/security/manager/ssl/src/nsNSSIOLayer.h @@ -55,81 +55,30 @@ #include "nsIAssociatedContentSecurity.h" #include "nsXPIDLString.h" #include "nsNSSShutDown.h" #include "nsIClientAuthDialogs.h" #include "nsAutoPtr.h" #include "nsNSSCertificate.h" #include "nsDataHashtable.h" -class nsIChannel; -class nsSSLThread; - -/* - * This class is used to store SSL socket I/O state information, - * that is not being executed directly, but defered to - * the separate SSL thread. - */ -class nsSSLSocketThreadData -{ -public: - nsSSLSocketThreadData(); - ~nsSSLSocketThreadData(); +namespace mozilla { - bool ensure_buffer_size(PRInt32 amount); - - enum ssl_state { - ssl_invalid, // used for initializating, should never occur - ssl_idle, // not in use by SSL thread, no activity pending - ssl_pending_write, // waiting for SSL thread to complete writing - ssl_pending_read, // waiting for SSL thread to complete reading - ssl_writing_done, // SSL write completed, results are ready - ssl_reading_done // SSL read completed, results are ready - }; - - ssl_state mSSLState; +class MutexAutoLock; - // Used to transport I/O error codes between SSL thread - // and initial caller thread. - PRErrorCode mPRErrorCode; +namespace psm { - // A buffer used to transfer I/O data between threads - char *mSSLDataBuffer; - PRInt32 mSSLDataBufferAllocatedSize; - - // The amount requested to read or write by the caller. - PRInt32 mSSLRequestedTransferAmount; +enum SSLErrorMessageType { + OverridableCertErrorMessage = 1, // for *overridable* certificate errors + PlainErrorMessage = 2 // all other errors (or "no error") +}; - // A pointer into our buffer, to the first byte - // that has not yet been delivered to the caller. - // Necessary, as the caller of the read function - // might request smaller chunks. - const char *mSSLRemainingReadResultData; - - // The caller previously requested to read or write. - // As the initial request to read or write is defered, - // the caller might (in theory) request smaller chunks - // in subsequent calls. - // This variable stores the amount of bytes successfully - // transfered, that have not yet been reported to the caller. - PRInt32 mSSLResultRemainingBytes; +} // namespace psm - // When defering SSL read/write activity to another thread, - // we switch the SSL level file descriptor of the original - // layered file descriptor to a pollable event, - // so we can wake up the original caller of the I/O function - // as soon as data is ready. - // This variable is used to save the SSL level file descriptor, - // to allow us to restore the original file descriptor layering. - PRFileDesc *mReplacedSSLFileDesc; - - bool mOneBytePendingFromEarlierWrite; - unsigned char mThePendingByte; - PRInt32 mOriginalRequestedTransferAmount; -}; +} // namespace mozilla class nsNSSSocketInfo : public nsITransportSecurityInfo, public nsISSLSocketControl, public nsIInterfaceRequestor, public nsISSLStatusProvider, public nsIAssociatedContentSecurity, public nsISerializable, public nsIClassInfo, @@ -148,102 +97,137 @@ public: NS_DECL_NSISSLSTATUSPROVIDER NS_DECL_NSIASSOCIATEDCONTENTSECURITY NS_DECL_NSISERIALIZABLE NS_DECL_NSICLASSINFO NS_DECL_NSICLIENTAUTHUSERDECISION nsresult SetSecurityState(PRUint32 aState); nsresult SetShortSecurityDescription(const PRUnichar *aText); - void SetErrorMessage(const PRUnichar *aText); nsresult SetForSTARTTLS(bool aForSTARTTLS); nsresult GetForSTARTTLS(bool *aForSTARTTLS); nsresult GetFileDescPtr(PRFileDesc** aFilePtr); nsresult SetFileDescPtr(PRFileDesc* aFilePtr); nsresult GetHandshakePending(bool *aHandshakePending); nsresult SetHandshakePending(bool aHandshakePending); + const char * GetHostName() const { + return mHostName.get(); + } nsresult GetHostName(char **aHostName); nsresult SetHostName(const char *aHostName); nsresult GetPort(PRInt32 *aPort); nsresult SetPort(PRInt32 aPort); void GetPreviousCert(nsIX509Cert** _result); - void SetCanceled(bool aCanceled); - bool GetCanceled(); + PRErrorCode GetErrorCode() const; + void SetCanceled(PRErrorCode errorCode, + ::mozilla::psm::SSLErrorMessageType errorMessageType); void SetHasCleartextPhase(bool aHasCleartextPhase); bool GetHasCleartextPhase(); void SetHandshakeInProgress(bool aIsIn); bool GetHandshakeInProgress() { return mHandshakeInProgress; } bool HandshakeTimeout(); void SetAllowTLSIntoleranceTimeout(bool aAllow); - bool GetExternalErrorReporting(); - nsresult RememberCAChain(CERTCertList *aCertList); /* Set SSL Status values */ nsresult SetSSLStatus(nsSSLStatus *aSSLStatus); nsSSLStatus* SSLStatus() { return mSSLStatus; } - - PRStatus CloseSocketAndDestroy(); + void SetStatusErrorBits(nsIX509Cert & cert, PRUint32 collected_errors); + + PRStatus CloseSocketAndDestroy( + const nsNSSShutDownPreventionLock & proofOfLock); bool IsCertIssuerBlacklisted() const { return mIsCertIssuerBlacklisted; } void SetCertIssuerBlacklisted() { mIsCertIssuerBlacklisted = true; } + + void SetNegotiatedNPN(const char *value, PRUint32 length); + void SetHandshakeCompleted() { mHandshakeCompleted = true; } + + bool GetJoined() { return mJoined; } + void SetSentClientCert() { mSentClientCert = true; } + + // XXX: These are only used on for diagnostic purposes + enum CertVerificationState { + before_cert_verification, + waiting_for_cert_verification, + after_cert_verification + }; + void SetCertVerificationWaiting(); + // Use errorCode == 0 to indicate success; in that case, errorMessageType is + // ignored. + void SetCertVerificationResult(PRErrorCode errorCode, + ::mozilla::psm::SSLErrorMessageType errorMessageType); + + // for logging only + PRBool IsWaitingForCertVerification() const + { + return mCertVerificationState == waiting_for_cert_verification; + } + + protected: + mutable ::mozilla::Mutex mMutex; + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; PRFileDesc* mFd; - enum { - blocking_state_unknown, is_nonblocking_socket, is_blocking_socket - } mBlockingState; + CertVerificationState mCertVerificationState; PRUint32 mSecurityState; PRInt32 mSubRequestsHighSecurity; PRInt32 mSubRequestsLowSecurity; PRInt32 mSubRequestsBrokenSecurity; PRInt32 mSubRequestsNoSecurity; nsString mShortDesc; - nsString mErrorMessage; + + PRErrorCode mErrorCode; + ::mozilla::psm::SSLErrorMessageType mErrorMessageType; + nsString mErrorMessageCached; + nsresult formatErrorMessage(::mozilla::MutexAutoLock const & proofOfLock); + bool mDocShellDependentStuffKnown; bool mExternalErrorReporting; // DocShellDependent bool mForSTARTTLS; bool mHandshakePending; - bool mCanceled; bool mHasCleartextPhase; bool mHandshakeInProgress; bool mAllowTLSIntoleranceTimeout; bool mRememberClientAuthCertificate; PRIntervalTime mHandshakeStartTime; PRInt32 mPort; nsXPIDLCString mHostName; PRErrorCode mIsCertIssuerBlacklisted; /* SSL Status */ nsRefPtr<nsSSLStatus> mSSLStatus; nsresult ActivateSSL(); - nsSSLSocketThreadData *mThreadData; + nsCString mNegotiatedNPN; + bool mNPNCompleted; + bool mHandshakeCompleted; + bool mJoined; + bool mSentClientCert; private: virtual void virtualDestroyNSSReference(); void destructorSafeDestroyNSSReference(); - -friend class nsSSLThread; }; class nsCStringHashSet; class nsSSLStatus; class nsNSSSocketInfo; class nsPSMRememberCertErrorsTable @@ -298,21 +282,16 @@ public: static void rememberTolerantSite(PRFileDesc* ssl_layer_fd, nsNSSSocketInfo *socketInfo); static void addIntolerantSite(const nsCString &str); static void removeIntolerantSite(const nsCString &str); static bool isKnownAsIntolerantSite(const nsCString &str); static void setRenegoUnrestrictedSites(const nsCString &str); static bool isRenegoUnrestrictedSite(const nsCString &str); - - static PRFileDesc *mSharedPollableEvent; - static nsNSSSocketInfo *mSocketOwningPollableEvent; - - static bool mPollableEventCurrentlySet; }; nsresult nsSSLIOLayerNewSocket(PRInt32 family, const char *host, PRInt32 port, const char *proxyHost, PRInt32 proxyPort, PRFileDesc **fd,
--- a/security/manager/ssl/src/nsSSLStatus.h +++ b/security/manager/ssl/src/nsSSLStatus.h @@ -69,16 +69,19 @@ public: PRUint32 mSecretKeyLength; nsXPIDLCString mCipherName; bool mIsDomainMismatch; bool mIsNotValidAtThisTime; bool mIsUntrusted; bool mHaveKeyLengthAndCipher; + + /* mHaveCertErrrorBits is relied on to determine whether or not a SPDY + connection is eligible for joining in nsNSSSocketInfo::JoinConnection() */ bool mHaveCertErrorBits; }; // 2c3837af-8b85-4a68-b0d8-0aed88985b32 #define NS_SSLSTATUS_CID \ { 0x2c3837af, 0x8b85, 0x4a68, \ { 0xb0, 0xd8, 0x0a, 0xed, 0x88, 0x98, 0x5b, 0x32 } }
deleted file mode 100644 --- a/security/manager/ssl/src/nsSSLThread.cpp +++ /dev/null @@ -1,1150 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Red Hat, Inc. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Kai Engert <kengert@redhat.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsThreadUtils.h" -#include "nsSSLThread.h" -#include "nsNSSIOLayer.h" - -#include "ssl.h" - -using namespace mozilla; - -#ifdef PR_LOGGING -extern PRLogModuleInfo* gPIPNSSLog; -#endif - -nsSSLThread::nsSSLThread() -: mBusySocket(nsnull), - mSocketScheduledToBeDestroyed(nsnull) -{ - NS_ASSERTION(!ssl_thread_singleton, "nsSSLThread is a singleton, caller attempts to create another instance!"); - - ssl_thread_singleton = this; -} - -nsSSLThread::~nsSSLThread() -{ - ssl_thread_singleton = nsnull; -} - -PRFileDesc *nsSSLThread::getRealSSLFD(nsNSSSocketInfo *si) -{ - if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle) - return nsnull; - - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (si->mThreadData->mReplacedSSLFileDesc) - { - return si->mThreadData->mReplacedSSLFileDesc; - } - else - { - return si->mFd->lower; - } -} - -PRStatus nsSSLThread::requestGetsockname(nsNSSSocketInfo *si, PRNetAddr *addr) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->getsockname(fd, addr); -} - -PRStatus nsSSLThread::requestGetpeername(nsNSSSocketInfo *si, PRNetAddr *addr) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->getpeername(fd, addr); -} - -PRStatus nsSSLThread::requestGetsocketoption(nsNSSSocketInfo *si, - PRSocketOptionData *data) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->getsocketoption(fd, data); -} - -PRStatus nsSSLThread::requestSetsocketoption(nsNSSSocketInfo *si, - const PRSocketOptionData *data) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->setsocketoption(fd, data); -} - -PRStatus nsSSLThread::requestConnectcontinue(nsNSSSocketInfo *si, - PRInt16 out_flags) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->connectcontinue(fd, out_flags); -} - -PRInt32 nsSSLThread::requestRecvMsgPeek(nsNSSSocketInfo *si, void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout) -{ - if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle) - { - PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); - return -1; - } - - // Socket is unusable - set error and return -1. See bug #480619. - if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) - { - PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); - return -1; - } - - PRFileDesc *realSSLFD; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (si == ssl_thread_singleton->mBusySocket) - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - - switch (si->mThreadData->mSSLState) - { - case nsSSLSocketThreadData::ssl_idle: - break; - - case nsSSLSocketThreadData::ssl_reading_done: - { - // we have data available that we can return - - // if there was a failure, just return the failure, - // but do not yet clear our state, that should happen - // in the call to "read". - - if (si->mThreadData->mSSLResultRemainingBytes < 0) { - if (si->mThreadData->mPRErrorCode != PR_SUCCESS) { - PR_SetError(si->mThreadData->mPRErrorCode, 0); - } - - return si->mThreadData->mSSLResultRemainingBytes; - } - - PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes); - - memcpy(buf, si->mThreadData->mSSLRemainingReadResultData, return_amount); - - return return_amount; - } - - case nsSSLSocketThreadData::ssl_writing_done: - case nsSSLSocketThreadData::ssl_pending_write: - case nsSSLSocketThreadData::ssl_pending_read: - - // for safety reasons, also return would_block on any other state, - // although this switch statement should be complete and list - // the appropriate behaviour for each state. - default: - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - } - - if (si->mThreadData->mReplacedSSLFileDesc) - { - realSSLFD = si->mThreadData->mReplacedSSLFileDesc; - } - else - { - realSSLFD = si->mFd->lower; - } - } - - return realSSLFD->methods->recv(realSSLFD, buf, amount, flags, timeout); -} - -nsresult nsSSLThread::requestActivateSSL(nsNSSSocketInfo *si) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return NS_ERROR_FAILURE; - - if (SECSuccess != SSL_OptionSet(fd, SSL_SECURITY, true)) - return NS_ERROR_FAILURE; - - if (SECSuccess != SSL_ResetHandshake(fd, false)) - return NS_ERROR_FAILURE; - - return NS_OK; -} - -PRInt16 nsSSLThread::requestPoll(nsNSSSocketInfo *si, PRInt16 in_flags, PRInt16 *out_flags) -{ - if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle) - return 0; - - *out_flags = 0; - - // Socket is unusable - set EXCEPT-flag and return. See bug #480619. - if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) - { - *out_flags |= PR_POLL_EXCEPT; - return in_flags; - } - - bool want_sleep_and_wakeup_on_any_socket_activity = false; - bool handshake_timeout = false; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (ssl_thread_singleton->mBusySocket) - { - // If there is currently any socket busy on the SSL thread, - // use our own poll method implementation. - - switch (si->mThreadData->mSSLState) - { - case nsSSLSocketThreadData::ssl_writing_done: - { - if (in_flags & PR_POLL_WRITE) - { - *out_flags |= PR_POLL_WRITE; - } - - return in_flags; - } - break; - - case nsSSLSocketThreadData::ssl_reading_done: - { - if (in_flags & PR_POLL_READ) - { - *out_flags |= PR_POLL_READ; - } - - return in_flags; - } - break; - - case nsSSLSocketThreadData::ssl_pending_write: - case nsSSLSocketThreadData::ssl_pending_read: - { - if (si == ssl_thread_singleton->mBusySocket) - { - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - // The lower layer of the socket is currently the pollable event, - // which signals the readable state. - - return PR_POLL_READ; - } - else - { - // Unfortunately we do not have a pollable event - // that we could use to wake up the caller, as soon - // as the previously requested I/O operation has completed. - // Therefore we must use a kind of busy wait, - // we want the caller to check again, whenever any - // activity is detected on the associated socket. - // Unfortunately this could mean, the caller will detect - // activity very often, until we are finally done with - // the previously requested action and are able to - // return the buffered result. - // As our real I/O activity is happening on the other thread - // let's sleep some cycles, in order to not waste all CPU - // resources. - // But let's make sure we do not hold our shared mutex - // while waiting, so let's leave this block first. - - want_sleep_and_wakeup_on_any_socket_activity = true; - break; - } - } - else - { - // We should never get here, well, at least not with the current - // implementation of SSL thread, where we have one worker only. - // While another socket is busy, this socket "si" - // can not be marked with pending I/O at the same time. - - NS_NOTREACHED("Socket not busy on SSL thread marked as pending"); - return 0; - } - } - break; - - case nsSSLSocketThreadData::ssl_idle: - { - if (si->mThreadData->mOneBytePendingFromEarlierWrite) - { - if (in_flags & PR_POLL_WRITE) - { - // In this scenario we always want the caller to immediately - // try a write again, because it might not wake up otherwise. - *out_flags |= PR_POLL_WRITE; - return in_flags; - } - } - - handshake_timeout = si->HandshakeTimeout(); - - if (si != ssl_thread_singleton->mBusySocket) - { - // Some other socket is currently busy on the SSL thread. - // It is possible that busy socket is currently blocked (e.g. by UI). - // Therefore we should not report "si" as being readable/writeable, - // regardless whether it is. - // (Because if we did report readable/writeable to the caller, - // the caller would repeatedly request us to do I/O, - // although our read/write function would not be able to fulfil - // the request, because our single worker is blocked). - // To avoid the unnecessary busy loop in that scenario, - // for socket "si" we report "not ready" to the caller. - // We do this by faking our caller did not ask for neither - // readable nor writeable when querying the lower layer. - // (this will leave querying for exceptions enabled) - - in_flags &= ~(PR_POLL_READ | PR_POLL_WRITE); - } - } - break; - - default: - break; - } - } - else - { - handshake_timeout = si->HandshakeTimeout(); - } - - if (handshake_timeout) - { - NS_ASSERTION(in_flags & PR_POLL_EXCEPT, "nsSSLThread::requestPoll handshake timeout, but caller did not poll for EXCEPT"); - - *out_flags |= PR_POLL_EXCEPT; - return in_flags; - } - } - - if (want_sleep_and_wakeup_on_any_socket_activity) - { - // This is where we wait for any socket activity, - // because we do not have a pollable event. - // XXX Will this really cause us to wake up - // whatever happens? - - PR_Sleep( PR_MillisecondsToInterval(1) ); - return PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT; - } - - return si->mFd->lower->methods->poll(si->mFd->lower, in_flags, out_flags); -} - -PRStatus nsSSLThread::requestClose(nsNSSSocketInfo *si) -{ - if (!ssl_thread_singleton || !si) - return PR_FAILURE; - - bool close_later = false; - nsCOMPtr<nsIRequest> requestToCancel; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (ssl_thread_singleton->mBusySocket == si) { - - // That's tricky, SSL thread is currently busy with this socket, - // and might even be blocked on it (UI or OCSP). - // We should not close the socket directly, but rather - // schedule closing it, at the time the SSL thread is done. - // If there is indeed a depending OCSP request pending, - // we should cancel it now. - - if (ssl_thread_singleton->mPendingHTTPRequest) - { - requestToCancel.swap(ssl_thread_singleton->mPendingHTTPRequest); - } - - close_later = true; - ssl_thread_singleton->mSocketScheduledToBeDestroyed = si; - - ssl_thread_singleton->mCond.NotifyAll(); - } - } - - if (requestToCancel) - { - if (NS_IsMainThread()) - { - requestToCancel->Cancel(NS_ERROR_ABORT); - } - else - { - NS_WARNING("Attempt to close SSL socket from a thread that is not the main thread. Can not cancel pending HTTP request from NSS"); - } - - requestToCancel = nsnull; - } - - if (!close_later) - { - return si->CloseSocketAndDestroy(); - } - - return PR_SUCCESS; -} - -void nsSSLThread::restoreOriginalSocket_locked(nsNSSSocketInfo *si) -{ - if (si->mThreadData->mReplacedSSLFileDesc) - { - if (nsSSLIOLayerHelpers::mPollableEventCurrentlySet) - { - nsSSLIOLayerHelpers::mPollableEventCurrentlySet = false; - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - PR_WaitForPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent); - } - } - - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - // need to restore - si->mFd->lower = si->mThreadData->mReplacedSSLFileDesc; - si->mThreadData->mReplacedSSLFileDesc = nsnull; - } - - nsSSLIOLayerHelpers::mSocketOwningPollableEvent = nsnull; - } -} - -PRStatus nsSSLThread::getRealFDIfBlockingSocket_locked(nsNSSSocketInfo *si, - PRFileDesc *&out_fd) -{ - out_fd = nsnull; - - PRFileDesc *realFD = - (si->mThreadData->mReplacedSSLFileDesc) ? - si->mThreadData->mReplacedSSLFileDesc : si->mFd->lower; - - if (si->mBlockingState == nsNSSSocketInfo::blocking_state_unknown) - { - PRSocketOptionData sod; - sod.option = PR_SockOpt_Nonblocking; - if (PR_GetSocketOption(realFD, &sod) == PR_FAILURE) - return PR_FAILURE; - - si->mBlockingState = sod.value.non_blocking ? - nsNSSSocketInfo::is_nonblocking_socket : nsNSSSocketInfo::is_blocking_socket; - } - - if (si->mBlockingState == nsNSSSocketInfo::is_blocking_socket) - { - out_fd = realFD; - } - - return PR_SUCCESS; -} - -PRInt32 nsSSLThread::requestRead(nsNSSSocketInfo *si, void *buf, PRInt32 amount, - PRIntervalTime timeout) -{ - if (!ssl_thread_singleton || !si || !buf || !amount || !ssl_thread_singleton->mThreadHandle) - { - PR_SetError(PR_UNKNOWN_ERROR, 0); - return -1; - } - - bool this_socket_is_busy = false; - bool some_other_socket_is_busy = false; - nsSSLSocketThreadData::ssl_state my_ssl_state = nsSSLSocketThreadData::ssl_invalid; - PRFileDesc *blockingFD = nsnull; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (ssl_thread_singleton->exitRequested(threadLock)) { - PR_SetError(PR_UNKNOWN_ERROR, 0); - return -1; - } - - if (getRealFDIfBlockingSocket_locked(si, blockingFD) == PR_FAILURE) { - return -1; - } - - if (!blockingFD) - { - my_ssl_state = si->mThreadData->mSSLState; - - if (ssl_thread_singleton->mBusySocket == si) - { - this_socket_is_busy = true; - - if (my_ssl_state == nsSSLSocketThreadData::ssl_reading_done) - { - // we will now care for the data that's ready, - // the socket is no longer busy on the ssl thread - - restoreOriginalSocket_locked(si); - - ssl_thread_singleton->mBusySocket = nsnull; - - // We'll handle the results further down, - // while not holding the lock. - } - } - else if (ssl_thread_singleton->mBusySocket) - { - some_other_socket_is_busy = true; - } - - if (!this_socket_is_busy && si->HandshakeTimeout()) - { - restoreOriginalSocket_locked(si); - PR_SetError(PR_CONNECT_RESET_ERROR, 0); - checkHandshake(-1, true, si->mFd->lower, si); - return -1; - } - } - // leave this mutex protected scope before the blockingFD handling - } - - if (blockingFD) - { - // this is an exception, we do not use our SSL thread at all, - // just pass the call through to libssl. - return blockingFD->methods->recv(blockingFD, buf, amount, 0, timeout); - } - - switch (my_ssl_state) - { - case nsSSLSocketThreadData::ssl_idle: - { - NS_ASSERTION(!this_socket_is_busy, "oops, unexpected incosistency"); - - if (some_other_socket_is_busy) - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - - // ssl thread is not busy, we'll continue below - } - break; - - case nsSSLSocketThreadData::ssl_reading_done: - // there has been a previous request to read, that is now done! - { - // failure ? - if (si->mThreadData->mSSLResultRemainingBytes < 0) { - if (si->mThreadData->mPRErrorCode != PR_SUCCESS) { - PR_SetError(si->mThreadData->mPRErrorCode, 0); - si->mThreadData->mPRErrorCode = PR_SUCCESS; - } - - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - return si->mThreadData->mSSLResultRemainingBytes; - } - - PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes); - - memcpy(buf, si->mThreadData->mSSLRemainingReadResultData, return_amount); - - si->mThreadData->mSSLResultRemainingBytes -= return_amount; - - if (!si->mThreadData->mSSLResultRemainingBytes) { - si->mThreadData->mSSLRemainingReadResultData = nsnull; - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - } - else { - si->mThreadData->mSSLRemainingReadResultData += return_amount; - } - - return return_amount; - } - // we never arrive here, see return statement above - break; - - - // We should not see the following events here, - // because we have not yet signaled Necko that we are - // readable/writable again, so if we end up here, - // it means that Necko decided to try read/write again, - // for whatever reason. No problem, just return would_block, - case nsSSLSocketThreadData::ssl_pending_write: - case nsSSLSocketThreadData::ssl_pending_read: - - // We should not see this state here, because Necko has previously - // requested us to write, Necko is not yet aware that it's done, - // (although it meanwhile is), but Necko now tries to read? - // If that ever happens, it's confusing, but not a problem, - // just let Necko know we can not do that now and return would_block. - case nsSSLSocketThreadData::ssl_writing_done: - - // for safety reasons, also return would_block on any other state, - // although this switch statement should be complete and list - // the appropriate behaviour for each state. - default: - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - // we never arrive here, see return statement above - break; - } - - if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) { - PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); - return -1; - } - - if (si->GetCanceled()) { - return PR_FAILURE; - } - - // si is idle and good, and no other socket is currently busy, - // so it's fine to continue with the request. - - if (!si->mThreadData->ensure_buffer_size(amount)) - { - PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); - return -1; - } - - si->mThreadData->mSSLRequestedTransferAmount = amount; - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_pending_read; - - // Remember we are operating on a layered file descriptor, that consists of - // a PSM code layer (nsNSSIOLayer), a NSS code layer (SSL protocol logic), - // and the raw socket at the bottommost layer. - // - // We don't want to call the SSL layer read/write directly on this thread, - // because it might block, should a callback to UI (for user confirmation) - // or Necko (for retrieving OCSP verification data) be necessary. - // As Necko is single threaded, it is currently waiting for this - // function to return, and a callback into Necko from NSS couldn't succeed. - // - // Therefore we must defer the request to read/write to a separate SSL thread. - // We will return WOULD_BLOCK to Necko, and will return the real results - // once the I/O operation on the SSL thread is ready. - // - // The tricky part is to wake up Necko, as soon as the I/O operation - // on the SSL thread is done. - // - // In order to achieve that, we manipulate the layering of the file - // descriptor. Usually the PSM layer points to the SSL layer as its lower - // layer. We change that to a pollable event file descriptor. - // - // Once we return from this original read/write function, Necko will - // poll/select on the file descriptor. As result data is not yet ready, we will - // instruct Necko to select on the bottommost file descriptor - // (by using appropriate flags in PSM's layer implementation of the - // poll method), which is the pollable event. - // - // Once the SSL thread is done with the call to the SSL layer, it will - // "set" the pollable event, causing Necko to wake up on the file descriptor - // and call read/write again. Now that the file descriptor is in the done state, - // we'll arrive in this read/write function again. We'll detect the socket is - // in the done state, and restore the original SSL level file descriptor. - // Finally, we return the data obtained on the SSL thread back to our caller. - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - NS_ASSERTION(!nsSSLIOLayerHelpers::mSocketOwningPollableEvent, - "oops, some other socket still owns our shared pollable event"); - - NS_ASSERTION(!si->mThreadData->mReplacedSSLFileDesc, "oops"); - - si->mThreadData->mReplacedSSLFileDesc = si->mFd->lower; - si->mFd->lower = nsSSLIOLayerHelpers::mSharedPollableEvent; - } - - nsSSLIOLayerHelpers::mSocketOwningPollableEvent = si; - ssl_thread_singleton->mBusySocket = si; - - // notify the thread - ssl_thread_singleton->mCond.NotifyAll(); - } - - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; -} - -PRInt32 nsSSLThread::requestWrite(nsNSSSocketInfo *si, const void *buf, PRInt32 amount, - PRIntervalTime timeout) -{ - if (!ssl_thread_singleton || !si || !buf || !amount || !ssl_thread_singleton->mThreadHandle) - { - PR_SetError(PR_UNKNOWN_ERROR, 0); - return -1; - } - - bool this_socket_is_busy = false; - bool some_other_socket_is_busy = false; - nsSSLSocketThreadData::ssl_state my_ssl_state = nsSSLSocketThreadData::ssl_invalid; - PRFileDesc *blockingFD = nsnull; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (ssl_thread_singleton->exitRequested(threadLock)) { - PR_SetError(PR_UNKNOWN_ERROR, 0); - return -1; - } - - if (getRealFDIfBlockingSocket_locked(si, blockingFD) == PR_FAILURE) { - return -1; - } - - if (!blockingFD) - { - my_ssl_state = si->mThreadData->mSSLState; - - if (ssl_thread_singleton->mBusySocket == si) - { - this_socket_is_busy = true; - - if (my_ssl_state == nsSSLSocketThreadData::ssl_writing_done) - { - // we will now care for the data that's ready, - // the socket is no longer busy on the ssl thread - - restoreOriginalSocket_locked(si); - - ssl_thread_singleton->mBusySocket = nsnull; - - // We'll handle the results further down, - // while not holding the lock. - } - } - else if (ssl_thread_singleton->mBusySocket) - { - some_other_socket_is_busy = true; - } - - if (!this_socket_is_busy && si->HandshakeTimeout()) - { - restoreOriginalSocket_locked(si); - PR_SetError(PR_CONNECT_RESET_ERROR, 0); - checkHandshake(-1, false, si->mFd->lower, si); - return -1; - } - } - // leave this mutex protected scope before the blockingFD handling - } - - if (blockingFD) - { - // this is an exception, we do not use our SSL thread at all, - // just pass the call through to libssl. - return blockingFD->methods->send(blockingFD, buf, amount, 0, timeout); - } - - switch (my_ssl_state) - { - case nsSSLSocketThreadData::ssl_idle: - { - NS_ASSERTION(!this_socket_is_busy, "oops, unexpected incosistency"); - - if (some_other_socket_is_busy) - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - - // ssl thread is not busy, we'll continue below - } - break; - - case nsSSLSocketThreadData::ssl_writing_done: - // there has been a previous request to write, that is now done! - { - // failure ? - if (si->mThreadData->mSSLResultRemainingBytes < 0) { - if (si->mThreadData->mPRErrorCode != PR_SUCCESS) { - PR_SetError(si->mThreadData->mPRErrorCode, 0); - si->mThreadData->mPRErrorCode = PR_SUCCESS; - } - - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - return si->mThreadData->mSSLResultRemainingBytes; - } - - nsSSLIOLayerHelpers::rememberTolerantSite(si->mFd, si); - - PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes); - - si->mThreadData->mSSLResultRemainingBytes -= return_amount; - - if (!si->mThreadData->mSSLResultRemainingBytes) { - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - } - - return return_amount; - } - break; - - // We should not see the following events here, - // because we have not yet signaled Necko that we are - // readable/writable again, so if we end up here, - // it means that Necko decided to try read/write again, - // for whatever reason. No problem, just return would_block, - case nsSSLSocketThreadData::ssl_pending_write: - case nsSSLSocketThreadData::ssl_pending_read: - - // We should not see this state here, because Necko has previously - // requested us to read, Necko is not yet aware that it's done, - // (although it meanwhile is), but Necko now tries to write? - // If that ever happens, it's confusing, but not a problem, - // just let Necko know we can not do that now and return would_block. - case nsSSLSocketThreadData::ssl_reading_done: - - // for safety reasons, also return would_block on any other state, - // although this switch statement should be complete and list - // the appropriate behaviour for each state. - default: - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - // we never arrive here, see return statement above - break; - } - - if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) { - PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); - return -1; - } - - if (si->GetCanceled()) { - return PR_FAILURE; - } - - // si is idle and good, and no other socket is currently busy, - // so it's fine to continue with the request. - - // However, use special handling for the - // mOneBytePendingFromEarlierWrite - // scenario, where we will not change any of our buffers at this point, - // as we are waiting for completion of the earlier write. - - if (!si->mThreadData->mOneBytePendingFromEarlierWrite) - { - if (!si->mThreadData->ensure_buffer_size(amount)) - { - PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); - return -1; - } - - memcpy(si->mThreadData->mSSLDataBuffer, buf, amount); - si->mThreadData->mSSLRequestedTransferAmount = amount; - } - - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_pending_write; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - NS_ASSERTION(!nsSSLIOLayerHelpers::mSocketOwningPollableEvent, - "oops, some other socket still owns our shared pollable event"); - - NS_ASSERTION(!si->mThreadData->mReplacedSSLFileDesc, "oops"); - - si->mThreadData->mReplacedSSLFileDesc = si->mFd->lower; - si->mFd->lower = nsSSLIOLayerHelpers::mSharedPollableEvent; - } - - nsSSLIOLayerHelpers::mSocketOwningPollableEvent = si; - ssl_thread_singleton->mBusySocket = si; - - ssl_thread_singleton->mCond.NotifyAll(); - } - - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; -} - -void nsSSLThread::Run(void) -{ - // Helper variable, we don't want to call destroy - // while holding the mutex. - nsNSSSocketInfo *socketToDestroy = nsnull; - - while (true) - { - if (socketToDestroy) - { - socketToDestroy->CloseSocketAndDestroy(); - socketToDestroy = nsnull; - } - - // remember whether we'll write or read - nsSSLSocketThreadData::ssl_state busy_socket_ssl_state; - - { - // In this scope we need mutex protection, - // as we find out what needs to be done. - - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (mSocketScheduledToBeDestroyed) - { - if (mBusySocket == mSocketScheduledToBeDestroyed) - { - // That's rare, but it happens. - // We have received a request to close the socket, - // although I/O results have not yet been consumed. - - restoreOriginalSocket_locked(mBusySocket); - - mBusySocket->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - mBusySocket = nsnull; - } - - socketToDestroy = mSocketScheduledToBeDestroyed; - mSocketScheduledToBeDestroyed = nsnull; - continue; // go back and finally destroy it, before doing anything else - } - - if (exitRequested(threadLock)) - break; - - bool pending_work = false; - - do - { - if (mBusySocket - && - (mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_pending_read - || - mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_pending_write)) - { - pending_work = true; - } - - if (!pending_work) - { - // no work to do ? let's wait a moment - - mCond.Wait(); - } - - } while (!pending_work && !exitRequested(threadLock) && - !mSocketScheduledToBeDestroyed); - - if (mSocketScheduledToBeDestroyed) - continue; - - if (exitRequested(threadLock)) - break; - - if (!pending_work) - continue; - - busy_socket_ssl_state = mBusySocket->mThreadData->mSSLState; - } - - { - // In this scope we need to make sure NSS does not go away - // while we are busy. - nsNSSShutDownPreventionLock locker; - - // Reference for shorter code and to avoid multiple dereferencing. - nsSSLSocketThreadData &bstd = *mBusySocket->mThreadData; - - PRFileDesc *realFileDesc = bstd.mReplacedSSLFileDesc; - if (!realFileDesc) - { - realFileDesc = mBusySocket->mFd->lower; - } - - if (nsSSLSocketThreadData::ssl_pending_write == busy_socket_ssl_state) - { - PRInt32 bytesWritten = 0; - - if (bstd.mOneBytePendingFromEarlierWrite) - { - // Let's try to flush the final pending byte (that libSSL might already have - // processed). Let's be correct and send the final byte from our buffer. - bytesWritten = realFileDesc->methods - ->write(realFileDesc, &bstd.mThePendingByte, 1); - -#ifdef DEBUG_SSL_VERBOSE - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes\n", (void*)realFileDesc, bytesWritten)); -#endif - - bytesWritten = checkHandshake(bytesWritten, false, realFileDesc, mBusySocket); - if (bytesWritten < 0) { - // give the error back to caller - bstd.mPRErrorCode = PR_GetError(); - } - else if (bytesWritten == 1) { - // Cool, all flushed now. We can exit the one-byte-pending mode, - // and report the full amount back to the caller. - bytesWritten = bstd.mOriginalRequestedTransferAmount; - bstd.mOriginalRequestedTransferAmount = 0; - bstd.mOneBytePendingFromEarlierWrite = false; - } - } - else - { - // standard code, try to write the buffer we've been given just now - bytesWritten = realFileDesc->methods - ->write(realFileDesc, - bstd.mSSLDataBuffer, - bstd.mSSLRequestedTransferAmount); - -#ifdef DEBUG_SSL_VERBOSE - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes (out of %d)\n", - (void*)realFileDesc, bytesWritten, bstd.mSSLRequestedTransferAmount)); -#endif - - bytesWritten = checkHandshake(bytesWritten, false, realFileDesc, mBusySocket); - if (bytesWritten < 0) { - // give the error back to caller - bstd.mPRErrorCode = PR_GetError(); - } - else if (bstd.mSSLRequestedTransferAmount > 1 && - bytesWritten == (bstd.mSSLRequestedTransferAmount - 1)) { - // libSSL signaled us a short write. - // While libSSL accepted all data, not all bytes were flushed to the OS socket. - bstd.mThePendingByte = *(bstd.mSSLDataBuffer + (bstd.mSSLRequestedTransferAmount-1)); - bytesWritten = -1; - bstd.mPRErrorCode = PR_WOULD_BLOCK_ERROR; - bstd.mOneBytePendingFromEarlierWrite = true; - bstd.mOriginalRequestedTransferAmount = bstd.mSSLRequestedTransferAmount; - } - } - - bstd.mSSLResultRemainingBytes = bytesWritten; - busy_socket_ssl_state = nsSSLSocketThreadData::ssl_writing_done; - } - else if (nsSSLSocketThreadData::ssl_pending_read == busy_socket_ssl_state) - { - PRInt32 bytesRead = realFileDesc->methods - ->read(realFileDesc, - bstd.mSSLDataBuffer, - bstd.mSSLRequestedTransferAmount); - -#ifdef DEBUG_SSL_VERBOSE - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] read %d bytes\n", (void*)realFileDesc, bytesRead)); -#endif - bytesRead = checkHandshake(bytesRead, true, realFileDesc, mBusySocket); - if (bytesRead < 0) { - // give the error back to caller - bstd.mPRErrorCode = PR_GetError(); - } - - bstd.mSSLResultRemainingBytes = bytesRead; - bstd.mSSLRemainingReadResultData = bstd.mSSLDataBuffer; - busy_socket_ssl_state = nsSSLSocketThreadData::ssl_reading_done; - } - } - - // avoid setting event repeatedly - bool needToSetPollableEvent = false; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - mBusySocket->mThreadData->mSSLState = busy_socket_ssl_state; - - if (!nsSSLIOLayerHelpers::mPollableEventCurrentlySet) - { - needToSetPollableEvent = true; - nsSSLIOLayerHelpers::mPollableEventCurrentlySet = true; - } - } - - if (needToSetPollableEvent && nsSSLIOLayerHelpers::mSharedPollableEvent) - { - // Wake up the file descriptor on the Necko thread, - // so it can fetch the results from the SSL I/O call - // that we just completed. - PR_SetPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent); - - // if we don't have a pollable event, we'll have to wake up - // the caller by other means. - } - } - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - if (mBusySocket) - { - restoreOriginalSocket_locked(mBusySocket); - mBusySocket = nsnull; - } - if (!nsSSLIOLayerHelpers::mPollableEventCurrentlySet) - { - nsSSLIOLayerHelpers::mPollableEventCurrentlySet = true; - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - PR_SetPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent); - } - } - postStoppedEventToMainThread(threadLock); - } -} - -bool nsSSLThread::stoppedOrStopping() -{ - if (!ssl_thread_singleton) - return false; - - return ssl_thread_singleton->exitRequestedNoLock(); -} - -nsSSLThread *nsSSLThread::ssl_thread_singleton = nsnull;
deleted file mode 100644 --- a/security/manager/ssl/src/nsSSLThread.h +++ /dev/null @@ -1,158 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Red Hat, Inc. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Kai Engert <kengert@redhat.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef _NSSSLTHREAD_H_ -#define _NSSSLTHREAD_H_ - -#include "nsCOMPtr.h" -#include "nsIRequest.h" -#include "nsPSMBackgroundThread.h" - -class nsNSSSocketInfo; -class nsIHttpChannel; - -class nsSSLThread : public nsPSMBackgroundThread -{ -private: - // We use mMutex contained in our base class - // to protect access to these variables: - // mBusySocket, mSocketScheduledToBeDestroyed - // and to nsSSLSocketThreadData::mSSLState - // while a socket is the busy socket. - - // We use mCond contained in our base class - // to notify the SSL thread that a new SSL I/O - // request has been queued for processing. - // It can be found in the mBusySocket variable, - // containing all details in its member. - - // A socket that is currently owned by the SSL thread - // and has pending SSL I/O activity or I/O results - // not yet fetched by the original caller. - nsNSSSocketInfo *mBusySocket; - - // A socket that should be closed and destroyed - // as soon as possible. The request was initiated by - // Necko, but it happened at a time when the SSL - // thread had ownership of the socket, so the request - // was delayed. It's now the responsibility of the - // SSL thread to close and destroy this socket. - nsNSSSocketInfo *mSocketScheduledToBeDestroyed; - - // Did we receive a request from NSS to fetch HTTP - // data on behalf of NSS? (Most likely this is a OCSP request) - // We track a handle to the HTTP request sent to Necko. - // As this HTTP request depends on some original SSL socket, - // we can use this handle to cancel the dependent HTTP request, - // should we be asked to close the original SSL socket. - nsCOMPtr<nsIRequest> mPendingHTTPRequest; - - virtual void Run(void); - - // Called from SSL thread only - static PRInt32 checkHandshake(PRInt32 bytesTransfered, - bool wasReading, - PRFileDesc* fd, - nsNSSSocketInfo *socketInfo); - - // Function can be called from either Necko or SSL thread - // Caller must lock mMutex before this call. - static void restoreOriginalSocket_locked(nsNSSSocketInfo *si); - - // Helper for requestSomething functions, - // caled from the Necko thread only. - static PRFileDesc *getRealSSLFD(nsNSSSocketInfo *si); - - // Support of blocking sockets is very rudimentary. - // We only support it because Mozilla's LDAP code requires blocking I/O. - // We do not support switching the blocking mode of a socket. - // We require the blocking state has been set prior to the first - // read/write call, and will stay that way for the remainder of the socket's lifetime. - // This function must be called while holding the lock. - // If the socket is a blocking socket, out_fd will contain the real FD, - // on a non-blocking socket out_fd will be nsnull. - // If there is a failure in obtaining the status of the socket, - // the function will return PR_FAILURE. - static PRStatus getRealFDIfBlockingSocket_locked(nsNSSSocketInfo *si, - PRFileDesc *&out_fd); -public: - nsSSLThread(); - ~nsSSLThread(); - - static nsSSLThread *ssl_thread_singleton; - - // All requestSomething functions are called from - // the Necko thread only. - - static PRInt32 requestRead(nsNSSSocketInfo *si, - void *buf, - PRInt32 amount, - PRIntervalTime timeout); - - static PRInt32 requestWrite(nsNSSSocketInfo *si, - const void *buf, - PRInt32 amount, - PRIntervalTime timeout); - - static PRInt16 requestPoll(nsNSSSocketInfo *si, - PRInt16 in_flags, - PRInt16 *out_flags); - - static PRInt32 requestRecvMsgPeek(nsNSSSocketInfo *si, void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout); - - static PRStatus requestClose(nsNSSSocketInfo *si); - - static PRStatus requestGetsockname(nsNSSSocketInfo *si, PRNetAddr *addr); - - static PRStatus requestGetpeername(nsNSSSocketInfo *si, PRNetAddr *addr); - - static PRStatus requestGetsocketoption(nsNSSSocketInfo *si, - PRSocketOptionData *data); - - static PRStatus requestSetsocketoption(nsNSSSocketInfo *si, - const PRSocketOptionData *data); - - static PRStatus requestConnectcontinue(nsNSSSocketInfo *si, - PRInt16 out_flags); - - static nsresult requestActivateSSL(nsNSSSocketInfo *si); - - static bool stoppedOrStopping(); -}; - -#endif //_NSSSLTHREAD_H_
--- a/security/nss/Makefile +++ b/security/nss/Makefile @@ -142,20 +142,20 @@ endif build_nspr: $(NSPR_CONFIG_STATUS) cd $(CORE_DEPTH)/../nsprpub/$(OBJDIR_NAME) ; $(MAKE) clobber_nspr: $(NSPR_CONFIG_STATUS) cd $(CORE_DEPTH)/../nsprpub/$(OBJDIR_NAME) ; $(MAKE) clobber build_dbm: -ifndef NSS_DISABLE_DBM +ifdef NSS_DISABLE_DBM + @echo "skipping the build of DBM" +else cd $(CORE_DEPTH)/dbm ; $(MAKE) export libs -else - echo "skipping the build of DBM" endif clobber_dbm: cd $(CORE_DEPTH)/dbm ; $(MAKE) clobber moz_import:: ifeq (,$(filter-out WIN%,$(OS_TARGET))) $(NSINSTALL) -D $(DIST)/include/nspr
--- a/security/nss/TAG-INFO +++ b/security/nss/TAG-INFO @@ -1,1 +1,1 @@ -NSS_3_13_1_RTM +NSS_3_13_2_BETA1
--- a/security/nss/cmd/lib/pppolicy.c +++ b/security/nss/cmd/lib/pppolicy.c @@ -32,17 +32,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Support for various policy related extensions * - * $Id: pppolicy.c,v 1.3 2005/02/22 20:02:22 wtchang%redhat.com Exp $ + * $Id: pppolicy.c,v 1.5 2011/11/16 19:12:30 kaie%kuix.de Exp $ */ #include "seccomon.h" #include "secport.h" #include "secder.h" #include "cert.h" #include "secoid.h" #include "secasn1.h"
--- a/security/nss/cmd/ssltap/ssltap.c +++ b/security/nss/cmd/ssltap/ssltap.c @@ -61,17 +61,17 @@ #include <string.h> #include <time.h> #include "plgetopt.h" #include "nss.h" #include "cert.h" #include "sslproto.h" -#define VERSIONSTRING "$Revision: 1.19 $ ($Date: 2010/02/16 18:56:47 $) $Author: wtc%google.com $" +#define VERSIONSTRING "$Revision: 1.20 $ ($Date: 2011/11/05 23:09:28 $) $Author: wtc%google.com $" struct _DataBufferList; struct _DataBuffer; typedef struct _DataBufferList { struct _DataBuffer *first,*last; int size; @@ -1511,21 +1511,21 @@ showErr(const char * msg) errString = "(no text available)"; PR_fprintf(PR_STDERR, "%s: Error %d: %s: %s", progName, err, errString, msg); } int main(int argc, char *argv[]) { char *hostname=NULL; PRUint16 rendport=DEFPORT,port; - PRHostEnt hp; + PRAddrInfo *ai; + void *iter; PRStatus r; PRNetAddr na_client,na_server,na_rend; PRFileDesc *s_server,*s_client,*s_rend; /*rendezvous */ - char netdbbuf[PR_NETDB_BUF_SIZE]; int c_count=0; PLOptState *optstate; PLOptStatus status; SECStatus rv; progName = argv[0]; optstate = PL_CreateOptState(argc,argv,"fvxhslp:"); while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { @@ -1586,24 +1586,24 @@ int main(int argc, char *argv[]) /* find the 'server' IP address so we don't have to look it up later */ if (fancy) { PR_fprintf(PR_STDOUT,"<HTML><HEAD><TITLE>SSLTAP output</TITLE></HEAD>\n"); PR_fprintf(PR_STDOUT,"<BODY><PRE>\n"); } PR_fprintf(PR_STDERR,"Looking up \"%s\"...\n", hostname); - r = PR_GetHostByName(hostname,netdbbuf,PR_NETDB_BUF_SIZE,&hp); - if (r) { + ai = PR_GetAddrInfoByName(hostname, PR_AF_UNSPEC, PR_AI_ADDRCONFIG); + if (!ai) { showErr("Host Name lookup failed\n"); exit(5); } - PR_EnumerateHostEnt(0,&hp,0,&na_server); - PR_InitializeNetAddr(PR_IpAddrNull,port,&na_server); + iter = NULL; + iter = PR_EnumerateAddrInfo(iter, ai, port, &na_server); /* set up the port which the client will connect to */ r = PR_InitializeNetAddr(PR_IpAddrAny,rendport,&na_rend); if (r == PR_FAILURE) { PR_fprintf(PR_STDERR, "PR_InitializeNetAddr(,%d,) failed with error %d\n",PR_GetError()); exit(0); } @@ -1636,17 +1636,17 @@ int main(int argc, char *argv[]) PRPollDesc pds[2]; s_client = PR_Accept(s_rend,&na_client,PR_SecondsToInterval(3600)); if (s_client == NULL) { showErr("accept timed out\n"); exit(7); } - s_server = PR_NewTCPSocket(); + s_server = PR_OpenTCPSocket(na_server.raw.family); if (s_server == NULL) { showErr("couldn't open new socket to connect to server \n"); exit(8); } r = PR_Connect(s_server,&na_server,PR_SecondsToInterval(5)); if ( r == PR_FAILURE )
--- a/security/nss/cmd/tstclnt/tstclnt.c +++ b/security/nss/cmd/tstclnt/tstclnt.c @@ -212,16 +212,18 @@ static void Usage(const char *progName) "-n nickname"); fprintf(stderr, "%-20s Bypass PKCS11 layer for SSL encryption and MACing.\n", "-B"); fprintf(stderr, "%-20s Disable SSL v2.\n", "-2"); fprintf(stderr, "%-20s Disable SSL v3.\n", "-3"); fprintf(stderr, "%-20s Disable TLS (SSL v3.1).\n", "-T"); fprintf(stderr, "%-20s Prints only payload data. Skips HTTP header.\n", "-S"); fprintf(stderr, "%-20s Client speaks first. \n", "-f"); + fprintf(stderr, "%-20s Use synchronous certificate validation " + "(required for SSL2)\n", "-O"); fprintf(stderr, "%-20s Override bad server cert. Make it OK.\n", "-o"); fprintf(stderr, "%-20s Disable SSL socket locking.\n", "-s"); fprintf(stderr, "%-20s Verbose progress reporting.\n", "-v"); fprintf(stderr, "%-20s Use export policy.\n", "-x"); fprintf(stderr, "%-20s Ping the server and then exit.\n", "-q"); fprintf(stderr, "%-20s Renegotiate N times (resuming session if N>1).\n", "-r N"); fprintf(stderr, "%-20s Enable the session ticket extension.\n", "-u"); fprintf(stderr, "%-20s Enable compression.\n", "-z"); @@ -288,30 +290,54 @@ disableAllSSLCiphers(void) fprintf(stderr, "SSL_CipherPrefSet didn't like value 0x%04x (i = %d): %s\n", suite, i, SECU_Strerror(err)); exit(2); } } } +typedef struct +{ + PRBool shouldPause; /* PR_TRUE if we should use asynchronous peer cert + * authentication */ + PRBool isPaused; /* PR_TRUE if libssl is waiting for us to validate the + * peer's certificate and restart the handshake. */ + void * dbHandle; /* Certificate database handle to use while + * authenticating the peer's certificate. */ +} ServerCertAuth; + /* * Callback is called when incoming certificate is not valid. * Returns SECSuccess to accept the cert anyway, SECFailure to reject. */ static SECStatus ownBadCertHandler(void * arg, PRFileDesc * socket) { PRErrorCode err = PR_GetError(); /* can log invalid cert here */ fprintf(stderr, "Bad server certificate: %d, %s\n", err, SECU_Strerror(err)); return SECSuccess; /* override, say it's OK. */ } +static SECStatus +ownAuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig, + PRBool isServer) +{ + ServerCertAuth * serverCertAuth = (ServerCertAuth *) arg; + + FPRINTF(stderr, "using asynchronous certificate validation\n", progName); + + PORT_Assert(serverCertAuth->shouldPause); + PORT_Assert(!serverCertAuth->isPaused); + serverCertAuth->isPaused = PR_TRUE; + return SECWouldBlock; +} + SECStatus own_GetClientAuthData(void * arg, PRFileDesc * socket, struct CERTDistNamesStr * caNames, struct CERTCertificateStr ** pRetCert, struct SECKEYPrivateKeyStr **pRetKey) { if (verbose > 1) { @@ -493,21 +519,47 @@ separateReqHeader(const PRFileDesc* outF } else if (((c) >= 'a') && ((c) <= 'f')) { \ i = (c) - 'a' + 10; \ } else if (((c) >= 'A') && ((c) <= 'F')) { \ i = (c) - 'A' + 10; \ } else { \ Usage(progName); \ } +static SECStatus +restartHandshakeAfterServerCertIfNeeded(PRFileDesc * fd, + ServerCertAuth * serverCertAuth, + PRBool override) +{ + SECStatus rv; + + if (!serverCertAuth->isPaused) + return SECSuccess; + + FPRINTF(stderr, "%s: handshake was paused by auth certificate hook\n", + progName); + + serverCertAuth->isPaused = PR_FALSE; + rv = SSL_AuthCertificate(serverCertAuth->dbHandle, fd, PR_TRUE, PR_FALSE); + if (rv != SECSuccess && override) { + rv = ownBadCertHandler(NULL, fd); + } + if (rv != SECSuccess) { + return rv; + } + + rv = SSL_RestartHandshakeAfterAuthCertificate(fd); + + return rv; +} + int main(int argc, char **argv) { PRFileDesc * s; PRFileDesc * std_out; - CERTCertDBHandle * handle; char * host = NULL; char * certDir = NULL; char * nickname = NULL; char * cipherString = NULL; char * tmp; int multiplier = 0; SECStatus rv; PRStatus status; @@ -525,51 +577,58 @@ int main(int argc, char **argv) int enableFalseStart = 0; PRSocketOptionData opt; PRNetAddr addr; PRPollDesc pollset[2]; PRBool pingServerFirst = PR_FALSE; PRBool clientSpeaksFirst = PR_FALSE; PRBool wrStarted = PR_FALSE; PRBool skipProtoHeader = PR_FALSE; + ServerCertAuth serverCertAuth; int headerSeparatorPtrnId = 0; int error = 0; PRUint16 portno = 443; char * hs1SniHostName = NULL; char * hs2SniHostName = NULL; PLOptState *optstate; PLOptStatus optstatus; PRStatus prStatus; + serverCertAuth.shouldPause = PR_TRUE; + serverCertAuth.isPaused = PR_FALSE; + serverCertAuth.dbHandle = NULL; + progName = strrchr(argv[0], '/'); if (!progName) progName = strrchr(argv[0], '\\'); progName = progName ? progName+1 : argv[0]; tmp = PR_GetEnv("NSS_DEBUG_TIMEOUT"); if (tmp && tmp[0]) { int sec = PORT_Atoi(tmp); if (sec > 0) { maxInterval = PR_SecondsToInterval(sec); } } optstate = PL_CreateOptState(argc, argv, - "23BSTW:a:c:d:fgh:m:n:op:qr:suvw:xz"); + "23BOSTW:a:c:d:fgh:m:n:op:qr:suvw:xz"); while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) { switch (optstate->option) { case '?': default : Usage(progName); break; case '2': disableSSL2 = 1; break; case '3': disableSSL3 = 1; break; case 'B': bypassPKCS11 = 1; break; + case 'O': serverCertAuth.shouldPause = PR_FALSE; break; + case 'S': skipProtoHeader = PR_TRUE; break; case 'T': disableTLS = 1; break; case 'a': if (!hs1SniHostName) { hs1SniHostName = PORT_Strdup(optstate->value); } else if (!hs2SniHostName) { hs2SniHostName = PORT_Strdup(optstate->value); @@ -645,24 +704,18 @@ int main(int argc, char **argv) } else { char *certDirTmp = certDir; certDir = SECU_ConfigDirectory(certDirTmp); PORT_Free(certDirTmp); } rv = NSS_Init(certDir); if (rv != SECSuccess) { SECU_PrintError(progName, "unable to open cert database"); -#if 0 - rv = CERT_OpenVolatileCertDB(handle); - CERT_SetDefaultCertDB(handle); -#else return 1; -#endif } - handle = CERT_GetDefaultCertDB(); /* set the policy bits true for all the cipher suites. */ if (useExportPolicy) NSS_SetExportPolicy(); else NSS_SetDomesticPolicy(); /* all the SSL2 and SSL3 cipher suites are enabled by default. */ @@ -871,17 +924,23 @@ int main(int argc, char **argv) rv = SSL_OptionSet(s, SSL_ENABLE_FALSE_START, enableFalseStart); if (rv != SECSuccess) { SECU_PrintError(progName, "error enabling false start"); return 1; } SSL_SetPKCS11PinArg(s, &pwdata); - SSL_AuthCertificateHook(s, SSL_AuthCertificate, (void *)handle); + serverCertAuth.dbHandle = CERT_GetDefaultCertDB(); + + if (serverCertAuth.shouldPause) { + SSL_AuthCertificateHook(s, ownAuthCertificate, &serverCertAuth); + } else { + SSL_AuthCertificateHook(s, SSL_AuthCertificate, serverCertAuth.dbHandle); + } if (override) { SSL_BadCertHook(s, ownBadCertHandler, NULL); } SSL_GetClientAuthDataHook(s, own_GetClientAuthData, (void *)nickname); SSL_HandshakeCallback(s, handshakeCallback, hs2SniHostName); if (hs1SniHostName) { SSL_SetURL(s, hs1SniHostName); } else { @@ -979,16 +1038,24 @@ int main(int argc, char **argv) ** socket, read data from socket and write to stdout. */ FPRINTF(stderr, "%s: ready...\n", progName); while (pollset[SSOCK_FD].in_flags | pollset[STDIN_FD].in_flags) { char buf[4000]; /* buffer for stdin */ int nb; /* num bytes read from stdin. */ + rv = restartHandshakeAfterServerCertIfNeeded(s, &serverCertAuth, + override); + if (rv != SECSuccess) { + error = 254; /* 254 (usually) means "handshake failed" */ + SECU_PrintError(progName, "authentication of server cert failed"); + goto done; + } + pollset[SSOCK_FD].out_flags = 0; pollset[STDIN_FD].out_flags = 0; FPRINTF(stderr, "%s: about to call PR_Poll !\n", progName); filesReady = PR_Poll(pollset, npds, PR_INTERVAL_NO_TIMEOUT); if (filesReady < 0) { SECU_PrintError(progName, "select failed"); error = 1; @@ -1037,16 +1104,25 @@ int main(int argc, char **argv) goto done; } cc = 0; } bufp += cc; nb -= cc; if (nb <= 0) break; + + rv = restartHandshakeAfterServerCertIfNeeded(s, + &serverCertAuth, override); + if (rv != SECSuccess) { + error = 254; /* 254 (usually) means "handshake failed" */ + SECU_PrintError(progName, "authentication of server cert failed"); + goto done; + } + pollset[SSOCK_FD].in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT; pollset[SSOCK_FD].out_flags = 0; FPRINTF(stderr, "%s: about to call PR_Poll on writable socket !\n", progName); cc = PR_Poll(pollset, 1, PR_INTERVAL_NO_TIMEOUT); FPRINTF(stderr, "%s: PR_Poll returned with writable socket !\n",
--- a/security/nss/lib/certdb/cert.h +++ b/security/nss/lib/certdb/cert.h @@ -32,17 +32,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * cert.h - public data structures and prototypes for the certificate library * - * $Id: cert.h,v 1.86 2011/07/24 13:48:09 wtc%google.com Exp $ + * $Id: cert.h,v 1.88 2011/11/16 19:12:32 kaie%kuix.de Exp $ */ #ifndef _CERT_H_ #define _CERT_H_ #include "utilrename.h" #include "plarena.h" #include "plhash.h"
--- a/security/nss/lib/certdb/certdb.c +++ b/security/nss/lib/certdb/certdb.c @@ -34,17 +34,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Certificate handling code * - * $Id: certdb.c,v 1.116 2011/08/05 01:13:14 wtc%google.com Exp $ + * $Id: certdb.c,v 1.120 2011/11/17 00:20:20 bsmith%mozilla.com Exp $ */ #include "nssilock.h" #include "prmon.h" #include "prtime.h" #include "cert.h" #include "certi.h" #include "secder.h" @@ -591,27 +591,16 @@ cert_ComputeCertType(CERTCertificate *ce SECSuccess){ if (basicConstraintPresent == PR_TRUE && (basicConstraint.isCA)) { nsCertType |= NS_CERT_TYPE_SSL_CA; } else { nsCertType |= NS_CERT_TYPE_SSL_SERVER; } } - /* Treat certs with step-up OID as also having SSL server type. */ - if (findOIDinOIDSeqByTagNum(extKeyUsage, - SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) == - SECSuccess){ - if (basicConstraintPresent == PR_TRUE && - (basicConstraint.isCA)) { - nsCertType |= NS_CERT_TYPE_SSL_CA; - } else { - nsCertType |= NS_CERT_TYPE_SSL_SERVER; - } - } if (findOIDinOIDSeqByTagNum(extKeyUsage, SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) == SECSuccess){ if (basicConstraintPresent == PR_TRUE && (basicConstraint.isCA)) { nsCertType |= NS_CERT_TYPE_SSL_CA; } else { nsCertType |= NS_CERT_TYPE_SSL_CLIENT;
--- a/security/nss/lib/certdb/certv3.c +++ b/security/nss/lib/certdb/certv3.c @@ -32,17 +32,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Code for dealing with X509.V3 extensions. * - * $Id: certv3.c,v 1.10 2007/10/12 01:44:40 julien.pierre.boogz%sun.com Exp $ + * $Id: certv3.c,v 1.12 2011/11/16 19:12:32 kaie%kuix.de Exp $ */ #include "cert.h" #include "secitem.h" #include "secoid.h" #include "secder.h" #include "secasn1.h" #include "certxutl.h"
--- a/security/nss/lib/certdb/polcyxtn.c +++ b/security/nss/lib/certdb/polcyxtn.c @@ -32,17 +32,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Support for various policy related extensions * - * $Id: polcyxtn.c,v 1.11 2008/02/13 04:03:19 julien.pierre.boogz%sun.com Exp $ + * $Id: polcyxtn.c,v 1.13 2011/11/16 19:12:32 kaie%kuix.de Exp $ */ #include "seccomon.h" #include "secport.h" #include "secder.h" #include "cert.h" #include "secoid.h" #include "secasn1.h"
--- a/security/nss/lib/certhigh/certvfypkix.c +++ b/security/nss/lib/certhigh/certvfypkix.c @@ -220,32 +220,27 @@ enum { typedef struct { SECCertUsage certUsage; PRUint32 ekuStringIndex; } SECCertUsageToEku; const SECCertUsageToEku certUsageEkuStringMap[] = { {certUsageSSLClient, ekuIndexSSLClient}, {certUsageSSLServer, ekuIndexSSLServer}, - {certUsageSSLServerWithStepUp, ekuIndexSSLServer}, /* need to add oids to - * the list of eku. - * see 390381*/ {certUsageSSLCA, ekuIndexSSLServer}, {certUsageEmailSigner, ekuIndexEmail}, {certUsageEmailRecipient, ekuIndexEmail}, {certUsageObjectSigner, ekuIndexCodeSigner}, {certUsageUserCertImport, ekuIndexUnknown}, {certUsageVerifyCA, ekuIndexUnknown}, {certUsageProtectedObjectSigner, ekuIndexUnknown}, {certUsageStatusResponder, ekuIndexStatusResponder}, {certUsageAnyCA, ekuIndexUnknown}, }; -#define CERT_USAGE_EKU_STRING_MAPS_TOTAL 12 - /* * FUNCTION: cert_NssCertificateUsageToPkixKUAndEKU * DESCRIPTION: * * Converts nss CERTCertificateUsage bit field to pkix key and * extended key usages. * * PARAMETERS: @@ -287,17 +282,17 @@ cert_NssCertificateUsageToPkixKUAndEKU( PKIX_ENTER(CERTVFYPKIX, "cert_NssCertificateUsageToPkixEku"); PKIX_NULLCHECK_TWO(ppkixEKUList, ppkixKU); PKIX_CHECK( PKIX_List_Create(&ekuOidsList, plContext), PKIX_LISTCREATEFAILED); - for (;i < CERT_USAGE_EKU_STRING_MAPS_TOTAL;i++) { + for (;i < PR_ARRAY_SIZE(certUsageEkuStringMap);i++) { const SECCertUsageToEku *usageToEkuElem = &certUsageEkuStringMap[i]; if (usageToEkuElem->certUsage == requiredCertUsage) { ekuIndex = usageToEkuElem->ekuStringIndex; break; } } if (ekuIndex != ekuIndexUnknown) {
--- a/security/nss/lib/ckfw/builtins/certdata.c +++ b/security/nss/lib/ckfw/builtins/certdata.c @@ -30,17 +30,17 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef DEBUG -static const char CVS_ID[] = "@(#) $RCSfile: certdata.txt,v $ $Revision: 1.79 $ $Date: 2011/09/02 19:40:56 $""; @(#) $RCSfile: certdata.perl,v $ $Revision: 1.13 $ $Date: 2010/03/26 22:06:47 $"; +static const char CVS_ID[] = "@(#) $RCSfile: certdata.c,v $ $Revision: 1.83 $ $Date: 2011/11/03 15:11:57 $""; @(#) $RCSfile: certdata.c,v $ $Revision: 1.83 $ $Date: 2011/11/03 15:11:57 $"; #endif /* DEBUG */ #ifndef BUILTINS_H #include "builtins.h" #endif /* BUILTINS_H */ static const CK_BBOOL ck_false = CK_FALSE; static const CK_BBOOL ck_true = CK_TRUE; @@ -1090,17 +1090,17 @@ static const CK_ATTRIBUTE_TYPE nss_built #ifdef DEBUG static const NSSItem nss_builtins_items_0 [] = { { (void *)&cko_data, (PRUint32)sizeof(CK_OBJECT_CLASS) }, { (void *)&ck_true, (PRUint32)sizeof(CK_BBOOL) }, { (void *)&ck_false, (PRUint32)sizeof(CK_BBOOL) }, { (void *)&ck_false, (PRUint32)sizeof(CK_BBOOL) }, { (void *)"CVS ID", (PRUint32)7 }, { (void *)"NSS", (PRUint32)4 }, - { (void *)"@(#) $RCSfile: certdata.txt,v $ $Revision: 1.79 $ $Date: 2011/09/02 19:40:56 $""; @(#) $RCSfile: certdata.perl,v $ $Revision: 1.13 $ $Date: 2010/03/26 22:06:47 $", (PRUint32)160 } + { (void *)"@(#) $RCSfile: certdata.c,v $ $Revision: 1.83 $ $Date: 2011/11/03 15:11:57 $""; @(#) $RCSfile: certdata.c,v $ $Revision: 1.83 $ $Date: 2011/11/03 15:11:57 $", (PRUint32)160 } }; #endif /* DEBUG */ static const NSSItem nss_builtins_items_1 [] = { { (void *)&cko_nss_builtin_root_list, (PRUint32)sizeof(CK_OBJECT_CLASS) }, { (void *)&ck_true, (PRUint32)sizeof(CK_BBOOL) }, { (void *)&ck_false, (PRUint32)sizeof(CK_BBOOL) }, { (void *)&ck_false, (PRUint32)sizeof(CK_BBOOL) }, { (void *)"Mozilla Builtin Roots", (PRUint32)22 }
--- a/security/nss/lib/ckfw/builtins/certdata.txt +++ b/security/nss/lib/ckfw/builtins/certdata.txt @@ -29,17 +29,17 @@ # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** -CVS_ID "@(#) $RCSfile: certdata.txt,v $ $Revision: 1.79 $ $Date: 2011/09/02 19:40:56 $" +CVS_ID "@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $" # # certdata.txt # # This file contains the object definitions for the certs and other # information "built into" NSS. # # Object definitions:
--- a/security/nss/lib/cryptohi/keyhi.h +++ b/security/nss/lib/cryptohi/keyhi.h @@ -30,17 +30,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: keyhi.h,v 1.18 2011/07/24 13:48:12 wtc%google.com Exp $ */ +/* $Id: keyhi.h,v 1.20 2011/11/16 19:12:33 kaie%kuix.de Exp $ */ #ifndef _KEYHI_H_ #define _KEYHI_H_ #include "plarena.h" #include "seccomon.h" #include "secoidt.h"
--- a/security/nss/lib/freebl/blapi.h +++ b/security/nss/lib/freebl/blapi.h @@ -32,17 +32,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: blapi.h,v 1.42 2011/10/04 22:05:53 wtc%google.com Exp $ */ +/* $Id: blapi.h,v 1.43 2011/10/29 23:28:45 wtc%google.com Exp $ */ #ifndef _BLAPI_H_ #define _BLAPI_H_ #include "blapit.h" #include "hasht.h" #include "alghmac.h" @@ -268,17 +268,17 @@ JPAKE_Sign(PLArenaPool * arena, const PQ SECItem * gv, SECItem * r); /* Given gx == g^x, verify the Schnorr zero-knowledge proof (gv, r) for the * value x using the specified hash algorithm and signer ID. * * The arena is *not* optional so do not pass NULL for the arena parameter. */ SECStatus -JPAKE_Verify(PRArenaPool * arena, const PQGParams * pqg, +JPAKE_Verify(PLArenaPool * arena, const PQGParams * pqg, HASH_HashType hashType, const SECItem * signerID, const SECItem * peerID, const SECItem * gx, const SECItem * gv, const SECItem * r); /* Call before round 2 with x2, s, and x2s all non-NULL. This will calculate * base = g^(x1+x3+x4) (mod p) and x2s = x2*s (mod q). The values to send in * round 2 (A and the proof of knowledge of x2s) can then be calculated with * JPAKE_Sign using pqg->base = base and x = x2s.
--- a/security/nss/lib/freebl/jpake.c +++ b/security/nss/lib/freebl/jpake.c @@ -217,17 +217,17 @@ cleanup: MP_TO_SEC_ERROR(err); rv = SECFailure; } return rv; } /* Verify a Schnorr signature generated by the peer in round 1 or round 2. */ SECStatus -JPAKE_Verify(PRArenaPool * arena, const PQGParams * pqg, HASH_HashType hashType, +JPAKE_Verify(PLArenaPool * arena, const PQGParams * pqg, HASH_HashType hashType, const SECItem * signerID, const SECItem * peerID, const SECItem * gx, const SECItem * gv, const SECItem * r) { SECStatus rv = SECSuccess; mp_err err; mp_int p; mp_int q; mp_int g;
--- a/security/nss/lib/nss/nss.def +++ b/security/nss/lib/nss/nss.def @@ -1023,8 +1023,14 @@ CERT_DestroyCERTRevocationFlags; ;+NSS_3.13 { # NSS 3.13 release ;+ global: ;;SECKEY_RSAPSSParamsTemplate DATA ; NSS_Get_SECKEY_RSAPSSParamsTemplate; NSS_GetVersion; ;+ local: ;+ *; ;+}; +;+NSS_3.13.2 { # NSS 3.13.2 release +;+ global: +PK11_ImportEncryptedPrivateKeyInfoAndReturnKey; +;+ local: +;+ *; +;+};
--- a/security/nss/lib/nss/nss.h +++ b/security/nss/lib/nss/nss.h @@ -31,17 +31,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: nss.h,v 1.86 2011/10/27 19:29:44 kaie%kuix.de Exp $ */ +/* $Id: nss.h,v 1.87 2011/10/27 19:39:00 kaie%kuix.de Exp $ */ #ifndef __nss_h_ #define __nss_h_ /* The private macro _NSS_ECC_STRING is for NSS internal use only. */ #ifdef NSS_ENABLE_ECC #ifdef NSS_ECC_MORE_THAN_SUITE_B #define _NSS_ECC_STRING " Extended ECC" @@ -61,22 +61,22 @@ /* * NSS's major version, minor version, patch level, build number, and whether * this is a beta release. * * The format of the version string should be * "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]" */ -#define NSS_VERSION "3.13.1.0" _NSS_ECC_STRING _NSS_CUSTOMIZED +#define NSS_VERSION "3.13.2.0" _NSS_ECC_STRING _NSS_CUSTOMIZED " Beta" #define NSS_VMAJOR 3 #define NSS_VMINOR 13 -#define NSS_VPATCH 1 +#define NSS_VPATCH 2 #define NSS_VBUILD 0 -#define NSS_BETA PR_FALSE +#define NSS_BETA PR_TRUE #ifndef RC_INVOKED #include "seccomon.h" typedef struct NSSInitParametersStr NSSInitParameters; /*
--- a/security/nss/lib/pk11wrap/pk11akey.c +++ b/security/nss/lib/pk11wrap/pk11akey.c @@ -1569,23 +1569,46 @@ PK11_MakeKEAPubKey(unsigned char *keyDat rv = SECITEM_CopyItem(arena, &pubk->u.fortezza.KEAKey, &pkData); if (rv != SECSuccess) { PORT_FreeArena (arena, PR_FALSE); return NULL; } return pubk; } +/* + * NOTE: This function doesn't return a SECKEYPrivateKey struct to represent + * the new private key object. If it were to create a session object that + * could later be looked up by its nickname, it would leak a SECKEYPrivateKey. + * So isPerm must be true. + */ SECStatus PK11_ImportEncryptedPrivateKeyInfo(PK11SlotInfo *slot, SECKEYEncryptedPrivateKeyInfo *epki, SECItem *pwitem, SECItem *nickname, SECItem *publicValue, PRBool isPerm, PRBool isPrivate, KeyType keyType, unsigned int keyUsage, void *wincx) { + if (!isPerm) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + return PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(slot, epki, + pwitem, nickname, publicValue, isPerm, isPrivate, keyType, + keyUsage, NULL, wincx); +} + +SECStatus +PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(PK11SlotInfo *slot, + SECKEYEncryptedPrivateKeyInfo *epki, SECItem *pwitem, + SECItem *nickname, SECItem *publicValue, PRBool isPerm, + PRBool isPrivate, KeyType keyType, + unsigned int keyUsage, SECKEYPrivateKey **privk, + void *wincx) +{ CK_MECHANISM_TYPE pbeMechType; SECItem *crypto_param = NULL; PK11SymKey *key = NULL; SECStatus rv = SECSuccess; CK_MECHANISM_TYPE cryptoMechType; SECKEYPrivateKey *privKey = NULL; PRBool faulty3DES = PR_FALSE; int usageCount = 0; @@ -1671,17 +1694,21 @@ try_faulty_3des: PORT_Assert(usage != NULL); PORT_Assert(usageCount != 0); privKey = PK11_UnwrapPrivKey(slot, key, cryptoMechType, crypto_param, &epki->encryptedData, nickname, publicValue, isPerm, isPrivate, key_type, usage, usageCount, wincx); if(privKey) { - SECKEY_DestroyPrivateKey(privKey); + if (privk) { + *privk = privKey; + } else { + SECKEY_DestroyPrivateKey(privKey); + } privKey = NULL; rv = SECSuccess; goto done; } /* if we are unable to import the key and the pbeMechType is * CKM_NETSCAPE_PBE_SHA1_TRIPLE_DES_CBC, then it is possible that * the encrypted blob was created with a buggy key generation method
--- a/security/nss/lib/pk11wrap/pk11pub.h +++ b/security/nss/lib/pk11wrap/pk11pub.h @@ -566,16 +566,21 @@ SECStatus PK11_ImportDERPrivateKeyInfoAn SECItem *derPKI, SECItem *nickname, SECItem *publicValue, PRBool isPerm, PRBool isPrivate, unsigned int usage, SECKEYPrivateKey** privk, void *wincx); SECStatus PK11_ImportEncryptedPrivateKeyInfo(PK11SlotInfo *slot, SECKEYEncryptedPrivateKeyInfo *epki, SECItem *pwitem, SECItem *nickname, SECItem *publicValue, PRBool isPerm, PRBool isPrivate, KeyType type, unsigned int usage, void *wincx); +SECStatus PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(PK11SlotInfo *slot, + SECKEYEncryptedPrivateKeyInfo *epki, SECItem *pwitem, + SECItem *nickname, SECItem *publicValue, PRBool isPerm, + PRBool isPrivate, KeyType type, + unsigned int usage, SECKEYPrivateKey** privk, void *wincx); SECKEYPrivateKeyInfo *PK11_ExportPrivateKeyInfo( CERTCertificate *cert, void *wincx); SECKEYEncryptedPrivateKeyInfo *PK11_ExportEncryptedPrivKeyInfo( PK11SlotInfo *slot, SECOidTag algTag, SECItem *pwitem, SECKEYPrivateKey *pk, int iteration, void *wincx); SECKEYEncryptedPrivateKeyInfo *PK11_ExportEncryptedPrivateKeyInfo( PK11SlotInfo *slot, SECOidTag algTag, SECItem *pwitem, CERTCertificate *cert, int iteration, void *wincx);
--- a/security/nss/lib/pkcs7/p7decode.c +++ b/security/nss/lib/pkcs7/p7decode.c @@ -33,17 +33,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * PKCS7 decoding, verification. * - * $Id: p7decode.c,v 1.26 2011/08/21 01:14:17 wtc%google.com Exp $ + * $Id: p7decode.c,v 1.28 2011/11/16 19:12:34 kaie%kuix.de Exp $ */ #include "p7local.h" #include "cert.h" /* XXX do not want to have to include */ #include "certdb.h" /* certdb.h -- the trust stuff needed by */ /* the add certificate code needs to get */
--- a/security/nss/lib/pkcs7/secpkcs7.h +++ b/security/nss/lib/pkcs7/secpkcs7.h @@ -32,17 +32,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Interface to the PKCS7 implementation. * - * $Id: secpkcs7.h,v 1.6 2008/06/14 14:20:25 wtc%google.com Exp $ + * $Id: secpkcs7.h,v 1.8 2011/11/16 19:12:34 kaie%kuix.de Exp $ */ #ifndef _SECPKCS7_H_ #define _SECPKCS7_H_ #include "seccomon.h" #include "secoidt.h"
--- a/security/nss/lib/pki/pki3hack.c +++ b/security/nss/lib/pki/pki3hack.c @@ -30,17 +30,17 @@ * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef DEBUG -static const char CVS_ID[] = "@(#) $RCSfile: pki3hack.c,v $ $Revision: 1.102 $ $Date: 2011/04/13 00:10:26 $"; +static const char CVS_ID[] = "@(#) $RCSfile: pki3hack.c,v $ $Revision: 1.105 $ $Date: 2011/11/17 00:20:21 $"; #endif /* DEBUG */ /* * Hacks to integrate NSS 3.4 and NSS 4.0 certificates. */ #ifndef NSSPKI_H #include "nsspki.h" @@ -587,20 +587,16 @@ cert_trust_from_stan_trust(NSSTrust *t, client = get_nss3trust_from_nss4trust(t->clientAuth); if (client & (CERTDB_TRUSTED_CA|CERTDB_NS_TRUSTED_CA)) { client &= ~(CERTDB_TRUSTED_CA|CERTDB_NS_TRUSTED_CA); rvTrust->sslFlags |= CERTDB_TRUSTED_CLIENT_CA; } rvTrust->sslFlags |= client; rvTrust->emailFlags = get_nss3trust_from_nss4trust(t->emailProtection); rvTrust->objectSigningFlags = get_nss3trust_from_nss4trust(t->codeSigning); - /* The cert is a valid step-up cert (in addition to/lieu of trust above */ - if (t->stepUpApproved) { - rvTrust->sslFlags |= CERTDB_GOVT_APPROVED_CA; - } return rvTrust; } CERTCertTrust * nssTrust_GetCERTCertTrustForCert(NSSCertificate *c, CERTCertificate *cc) { CERTCertTrust *rvTrust = NULL; NSSTrustDomain *td = STAN_GetDefaultTrustDomain();
--- a/security/nss/lib/softoken/jpakesftk.c +++ b/security/nss/lib/softoken/jpakesftk.c @@ -69,19 +69,19 @@ jpake_Sign(PLArenaPool * arena, const PQ gx.data = NULL; gv.data = NULL; r.data = NULL; crv = jpake_mapStatus(JPAKE_Sign(arena, pqg, hashType, signerID, x, NULL, NULL, &gx, &gv, &r), CKR_MECHANISM_PARAM_INVALID); if (crv == CKR_OK) { - if (out->pGX != NULL && out->ulGXLen >= gx.len || - out->pGV != NULL && out->ulGVLen >= gv.len || - out->pR != NULL && out->ulRLen >= r.len) { + if ((out->pGX != NULL && out->ulGXLen >= gx.len) || + (out->pGV != NULL && out->ulGVLen >= gv.len) || + (out->pR != NULL && out->ulRLen >= r.len)) { PORT_Memcpy(out->pGX, gx.data, gx.len); PORT_Memcpy(out->pGV, gv.data, gv.len); PORT_Memcpy(out->pR, r.data, r.len); out->ulGXLen = gx.len; out->ulGVLen = gv.len; out->ulRLen = r.len; } else { crv = CKR_MECHANISM_PARAM_INVALID; @@ -103,31 +103,16 @@ jpake_Verify(PLArenaPool * arena, const r.data = publicValueIn->pR; r.len = publicValueIn->ulRLen; return jpake_mapStatus(JPAKE_Verify(arena, pqg, hashType, signerID, &peerID, &gx, &gv, &r), CKR_MECHANISM_PARAM_INVALID); } #define NUM_ELEM(x) (sizeof (x) / sizeof (x)[0]) -/* Ensure that the key is of the given type. */ -static CK_RV -jpake_ensureKeyType(SFTKObject * key, CK_KEY_TYPE keyType) -{ - CK_RV crv; - SFTKAttribute * keyTypeAttr = sftk_FindAttribute(key, CKA_KEY_TYPE); - crv = keyTypeAttr != NULL && - *(CK_KEY_TYPE *)keyTypeAttr->attrib.pValue == keyType - ? CKR_OK - : CKR_TEMPLATE_INCONSISTENT; - if (keyTypeAttr != NULL) - sftk_FreeAttribute(keyTypeAttr); - return crv; -} - /* If the template has the key type set, ensure that it was set to the correct * value. If the template did not have the key type set, set it to the * correct value. */ static CK_RV jpake_enforceKeyType(SFTKObject * key, CK_KEY_TYPE keyType) { CK_RV crv; SFTKAttribute * keyTypeAttr = sftk_FindAttribute(key, CKA_KEY_TYPE);
--- a/security/nss/lib/softoken/legacydb/config.mk +++ b/security/nss/lib/softoken/legacydb/config.mk @@ -33,21 +33,20 @@ # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** # $(PROGRAM) has explicit dependencies on $(EXTRA_LIBS) CRYPTOLIB=$(DIST)/lib/$(LIB_PREFIX)freebl.$(LIB_SUFFIX) -EXTRA_LIBS += $(CRYPTOLIB) - -ifndef NSS_DISABLE_DBM -EXTRA_LIBS += $(DIST)/lib/$(LIB_PREFIX)dbm.$(LIB_SUFFIX) -endif +EXTRA_LIBS += \ + $(CRYPTOLIB) \ + $(DIST)/lib/$(LIB_PREFIX)dbm.$(LIB_SUFFIX) \ + $(NULL) # can't do this in manifest.mn because OS_TARGET isn't defined there. ifeq (,$(filter-out WIN%,$(OS_TARGET))) # don't want the 32 in the shared library name SHARED_LIBRARY = $(OBJDIR)/$(DLL_PREFIX)$(LIBRARY_NAME)$(LIBRARY_VERSION).$(DLL_SUFFIX) IMPORT_LIBRARY = $(OBJDIR)/$(IMPORT_LIB_PREFIX)$(LIBRARY_NAME)$(LIBRARY_VERSION)$(IMPORT_LIB_SUFFIX)
--- a/security/nss/lib/softoken/softkver.h +++ b/security/nss/lib/softoken/softkver.h @@ -52,16 +52,16 @@ /* * Softoken's major version, minor version, patch level, build number, * and whether this is a beta release. * * The format of the version string should be * "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]" */ -#define SOFTOKEN_VERSION "3.13.1.0" SOFTOKEN_ECC_STRING +#define SOFTOKEN_VERSION "3.13.2.0" SOFTOKEN_ECC_STRING " Beta" #define SOFTOKEN_VMAJOR 3 #define SOFTOKEN_VMINOR 13 -#define SOFTOKEN_VPATCH 1 +#define SOFTOKEN_VPATCH 2 #define SOFTOKEN_VBUILD 0 -#define SOFTOKEN_BETA PR_FALSE +#define SOFTOKEN_BETA PR_TRUE #endif /* _SOFTKVER_H_ */
--- a/security/nss/lib/ssl/SSLerrs.h +++ b/security/nss/lib/ssl/SSLerrs.h @@ -400,8 +400,20 @@ ER3(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, ER3(SSL_ERROR_UNSAFE_NEGOTIATION, (SSL_ERROR_BASE + 113), "Peer attempted old style (potentially vulnerable) handshake.") ER3(SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD, (SSL_ERROR_BASE + 114), "SSL received an unexpected uncompressed record.") ER3(SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY, (SSL_ERROR_BASE + 115), "SSL received a weak ephemeral Diffie-Hellman key in Server Key Exchange handshake message.") + +ER3(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID, (SSL_ERROR_BASE + 116), +"SSL received invalid NPN extension data.") + +ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2, (SSL_ERROR_BASE + 117), +"SSL feature not supported for SSL 2.0 connections.") + +ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS, (SSL_ERROR_BASE + 118), +"SSL feature not supported for servers.") + +ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_CLIENTS, (SSL_ERROR_BASE + 119), +"SSL feature not supported for clients.")
--- a/security/nss/lib/ssl/ssl.def +++ b/security/nss/lib/ssl/ssl.def @@ -159,8 +159,17 @@ SSL_ConfigSecureServerWithCertChain; ;+*; ;+}; ;+NSS_3.13 { # NSS 3.13 release ;+ global: NSSSSL_GetVersion; ;+ local: ;+ *; ;+}; +;+NSS_3.13.2 { # NSS 3.13.2 release +;+ global: +SSL_SetNextProtoCallback; +SSL_SetNextProtoNego; +SSL_GetNextProto; +SSL_RestartHandshakeAfterAuthCertificate; +;+ local: +;+ *; +;+};
--- a/security/nss/lib/ssl/ssl.h +++ b/security/nss/lib/ssl/ssl.h @@ -31,17 +31,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: ssl.h,v 1.44 2011/10/06 22:42:33 wtc%google.com Exp $ */ +/* $Id: ssl.h,v 1.45 2011/10/29 00:29:11 bsmith%mozilla.com Exp $ */ #ifndef __ssl_h_ #define __ssl_h_ #include "prtypes.h" #include "prerror.h" #include "prio.h" #include "seccomon.h" @@ -176,16 +176,72 @@ SSL_IMPORT SECStatus SSL_EnableDefault(i /* New function names */ SSL_IMPORT SECStatus SSL_OptionSet(PRFileDesc *fd, PRInt32 option, PRBool on); SSL_IMPORT SECStatus SSL_OptionGet(PRFileDesc *fd, PRInt32 option, PRBool *on); SSL_IMPORT SECStatus SSL_OptionSetDefault(PRInt32 option, PRBool on); SSL_IMPORT SECStatus SSL_OptionGetDefault(PRInt32 option, PRBool *on); SSL_IMPORT SECStatus SSL_CertDBHandleSet(PRFileDesc *fd, CERTCertDBHandle *dbHandle); +/* SSLNextProtoCallback is called during the handshake for the client, when a + * Next Protocol Negotiation (NPN) extension has been received from the server. + * |protos| and |protosLen| define a buffer which contains the server's + * advertisement. This data is guaranteed to be well formed per the NPN spec. + * |protoOut| is a buffer provided by the caller, of length 255 (the maximum + * allowed by the protocol). On successful return, the protocol to be announced + * to the server will be in |protoOut| and its length in |*protoOutLen|. + * + * The callback must return SECFailure or SECSuccess (not SECWouldBlock). + */ +typedef SECStatus (PR_CALLBACK *SSLNextProtoCallback)( + void *arg, + PRFileDesc *fd, + const unsigned char* protos, + unsigned int protosLen, + unsigned char* protoOut, + unsigned int* protoOutLen, + unsigned int protoMaxOut); + +/* SSL_SetNextProtoCallback sets a callback function to handle Next Protocol + * Negotiation. It causes a client to advertise NPN. */ +SSL_IMPORT SECStatus SSL_SetNextProtoCallback(PRFileDesc *fd, + SSLNextProtoCallback callback, + void *arg); + +/* SSL_SetNextProtoNego can be used as an alternative to + * SSL_SetNextProtoCallback. It also causes a client to advertise NPN and + * installs a default callback function which selects the first supported + * protocol in server-preference order. If no matching protocol is found it + * selects the first supported protocol. + * + * The supported protocols are specified in |data| in wire-format (8-bit + * length-prefixed). For example: "\010http/1.1\006spdy/2". */ +SSL_IMPORT SECStatus SSL_SetNextProtoNego(PRFileDesc *fd, + const unsigned char *data, + unsigned int length); + +typedef enum SSLNextProtoState { + SSL_NEXT_PROTO_NO_SUPPORT = 0, /* No peer support */ + SSL_NEXT_PROTO_NEGOTIATED = 1, /* Mutual agreement */ + SSL_NEXT_PROTO_NO_OVERLAP = 2 /* No protocol overlap found */ +} SSLNextProtoState; + +/* SSL_GetNextProto can be used in the HandshakeCallback or any time after + * a handshake to retrieve the result of the Next Protocol negotiation. + * + * The length of the negotiated protocol, if any, is written into *bufLen. + * If the negotiated protocol is longer than bufLenMax, then SECFailure is + * returned. Otherwise, the negotiated protocol, if any, is written into buf, + * and SECSuccess is returned. */ +SSL_IMPORT SECStatus SSL_GetNextProto(PRFileDesc *fd, + SSLNextProtoState *state, + unsigned char *buf, + unsigned int *bufLen, + unsigned int bufLenMax); + /* ** Control ciphers that SSL uses. If on is non-zero then the named cipher ** is enabled, otherwise it is disabled. ** The "cipher" values are defined in sslproto.h (the SSL_EN_* values). ** EnableCipher records user preferences. ** SetPolicy sets the policy according to the policy module. */ #ifdef SSL_DEPRECATED_FUNCTION @@ -278,16 +334,29 @@ SSL_IMPORT SECStatus SSL_SecurityStatus( ** "fd" the socket "file" descriptor */ SSL_IMPORT CERTCertificate *SSL_PeerCertificate(PRFileDesc *fd); /* ** Authenticate certificate hook. Called when a certificate comes in ** (because of SSL_REQUIRE_CERTIFICATE in SSL_Enable) to authenticate the ** certificate. +** +** The authenticate certificate hook must return SECSuccess to indicate the +** certificate is valid, SECFailure to indicate the certificate is invalid, +** or SECWouldBlock if the application will authenticate the certificate +** asynchronously. +** +** If the authenticate certificate hook returns SECFailure, then the bad cert +** hook will be called. The bad cert handler is NEVER called if the +** authenticate certificate hook returns SECWouldBlock. +** +** See the documentation for SSL_RestartHandshakeAfterAuthCertificate for more +** information about the asynchronous behavior that occurs when the +** authenticate certificate hook returns SECWouldBlock. */ typedef SECStatus (PR_CALLBACK *SSLAuthCertificate)(void *arg, PRFileDesc *fd, PRBool checkSig, PRBool isServer); SSL_IMPORT SECStatus SSL_AuthCertificateHook(PRFileDesc *fd, SSLAuthCertificate f, void *arg); @@ -381,16 +450,25 @@ SSL_IMPORT PRFileDesc *SSL_ReconfigFD(PR * a - pkcs11 application specific data */ SSL_IMPORT SECStatus SSL_SetPKCS11PinArg(PRFileDesc *fd, void *a); /* ** This is a callback for dealing with server certs that are not authenticated ** by the client. The client app can decide that it actually likes the ** cert by some external means and restart the connection. +** +** The bad cert hook must return SECSuccess to override the result of the +** authenticate certificate hook, SECFailure if the certificate should still be +** considered invalid, or SECWouldBlock if the application will authenticate +** the certificate asynchronously. +** +** See the documentation for SSL_RestartHandshakeAfterAuthCertificate for more +** information about the asynchronous behavior that occurs when the bad cert +** hook returns SECWouldBlock. */ typedef SECStatus (PR_CALLBACK *SSLBadCertHandler)(void *arg, PRFileDesc *fd); SSL_IMPORT SECStatus SSL_BadCertHook(PRFileDesc *fd, SSLBadCertHandler f, void *arg); /* ** Configure SSL socket for running a secure server. Needs the ** certificate for the server and the servers private key. The arguments @@ -679,11 +757,58 @@ SSL_IMPORT SECStatus SSL_HandshakeNegoti */ extern PRBool NSSSSL_VersionCheck(const char *importedVersion); /* * Returns a const string of the SSL library version. */ extern const char *NSSSSL_GetVersion(void); +/* Restart an SSL connection that was paused to do asynchronous certificate + * chain validation (when the auth certificate hook or bad cert handler + * returned SECWouldBlock). + * + * Currently, this function works only for the client role of a connection; it + * does not work for the server role. + * + * The application MUST call SSL_RestartHandshakeAfterAuthCertificate after it + * has successfully validated the peer's certificate to continue the SSL + * handshake. + * + * The application MUST NOT call SSL_RestartHandshakeAfterAuthCertificate when + * certificate validation fails; instead, it should just close the connection. + * + * This function will not complete the entire handshake. The application must + * call SSL_ForceHandshake, PR_Recv, PR_Send, etc. after calling this function + * to force the handshake to complete. + * + * libssl will wait for the peer's certificate to be authenticated before + * calling the handshake callback, sending a client certificate, + * sending any application data, or returning any application data to the + * application (on the first handshake on a connection only). + * + * However, libssl may send and receive handshake messages while waiting for + * the application to call SSL_RestartHandshakeAfterAuthCertificate, and it may + * call other callbacks (e.g, the client auth data hook) before + * SSL_RestartHandshakeAfterAuthCertificate has been called. + * + * An application that uses this asynchronous mechanism will usually have lower + * handshake latency if it has to do public key operations on the certificate + * chain during the authentication, especially if it does so in parallel on + * another thread. However, if the application can authenticate the peer's + * certificate quickly then it may be more efficient to use the synchronous + * mechanism (i.e. returning SECFailure/SECSuccess instead of SECWouldBlock + * from the authenticate certificate hook). + * + * Be careful about converting an application from synchronous cert validation + * to asynchronous certificate validation. A naive conversion is likely to + * result in deadlocks; e.g. the application will wait in PR_Poll for network + * I/O on the connection while all network I/O on the connection is blocked + * waiting for this function to be called. + * + * Returns SECFailure on failure, SECSuccess on success. Never returns + * SECWouldBlock. + */ +SSL_IMPORT SECStatus SSL_RestartHandshakeAfterAuthCertificate(PRFileDesc *fd); + SEC_END_PROTOS #endif /* __ssl_h_ */
--- a/security/nss/lib/ssl/ssl3con.c +++ b/security/nss/lib/ssl/ssl3con.c @@ -34,17 +34,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: ssl3con.c,v 1.152 2011/10/01 03:59:54 bsmith%mozilla.com Exp $ */ +/* $Id: ssl3con.c,v 1.158 2011/11/19 21:58:21 bsmith%mozilla.com Exp $ */ #include "cert.h" #include "ssl.h" #include "cryptohi.h" /* for DSAU_ stuff */ #include "keyhi.h" #include "secder.h" #include "secitem.h" @@ -76,16 +76,17 @@ static PK11SymKey *ssl3_GenerateRSAPMS(s PK11SlotInfo * serverKeySlot); static SECStatus ssl3_DeriveMasterSecret(sslSocket *ss, PK11SymKey *pms); static SECStatus ssl3_DeriveConnectionKeysPKCS11(sslSocket *ss); static SECStatus ssl3_HandshakeFailure( sslSocket *ss); static SECStatus ssl3_InitState( sslSocket *ss); static SECStatus ssl3_SendCertificate( sslSocket *ss); static SECStatus ssl3_SendEmptyCertificate( sslSocket *ss); static SECStatus ssl3_SendCertificateRequest(sslSocket *ss); +static SECStatus ssl3_SendNextProto( sslSocket *ss); static SECStatus ssl3_SendFinished( sslSocket *ss, PRInt32 flags); static SECStatus ssl3_SendServerHello( sslSocket *ss); static SECStatus ssl3_SendServerHelloDone( sslSocket *ss); static SECStatus ssl3_SendServerKeyExchange( sslSocket *ss); static SECStatus ssl3_NewHandshakeHashes( sslSocket *ss); static SECStatus ssl3_UpdateHandshakeHashes( sslSocket *ss, const unsigned char *b, unsigned int l); @@ -232,19 +233,16 @@ static const /*SSL3ClientCertificateType * make sure there is room in the write buffer for padding and * other compression and cryptographic expansions. */ #define SSL3_BUFFER_FUDGE 100 + SSL3_COMPRESSION_MAX_EXPANSION #define EXPORT_RSA_KEY_LENGTH 64 /* bytes */ -/* This is a hack to make sure we don't do double handshakes for US policy */ -PRBool ssl3_global_policy_some_restricted = PR_FALSE; - /* This global item is used only in servers. It is is initialized by ** SSL_ConfigSecureServer(), and is used in ssl3_SendCertificateRequest(). */ CERTDistNames *ssl3_server_ca_list = NULL; static SSL3Statistics ssl3stats; /* indexed by SSL3BulkCipher */ static const ssl3BulkCipherDef bulk_cipher_defs[] = { @@ -3754,17 +3752,16 @@ done: } /************************************************************************** * end of Handshake Hash functions. * Begin Send and Handle functions for handshakes. **************************************************************************/ /* Called from ssl3_HandleHelloRequest(), - * ssl3_HandleFinished() (for step-up) * ssl3_RedoHandshake() * ssl2_BeginClientHandshake (when resuming ssl3 session) */ SECStatus ssl3_SendClientHello(sslSocket *ss) { sslSessionID * sid; ssl3CipherSpec * cwSpec; @@ -5578,17 +5575,17 @@ ssl3_HandleCertificateRequest(sslSocket /* XXX Should pass cert_types in this call!! */ rv = (SECStatus)(*ss->getClientAuthData)(ss->getClientAuthDataArg, ss->fd, &ca_list, &ss->ssl3.clientCertificate, &ss->ssl3.clientPrivateKey); } switch (rv) { case SECWouldBlock: /* getClientAuthData has put up a dialog box. */ - ssl_SetAlwaysBlock(ss); + ssl3_SetAlwaysBlock(ss); break; /* not an error */ case SECSuccess: /* check what the callback function returned */ if ((!ss->ssl3.clientCertificate) || (!ss->ssl3.clientPrivateKey)) { /* we are missing either the key or cert */ if (ss->ssl3.clientCertificate) { /* got a cert, but no key - free it */ @@ -5647,152 +5644,168 @@ loser: PORT_SetError(errCode); rv = SECFailure; done: if (arena != NULL) PORT_FreeArena(arena, PR_FALSE); return rv; } -/* - * attempt to restart the handshake after asynchronously handling - * a request for the client's certificate. - * - * inputs: - * cert Client cert chosen by application. - * Note: ssl takes this reference, and does not bump the - * reference count. The caller should drop its reference - * without calling CERT_DestroyCert after calling this function. - * - * key Private key associated with cert. This function makes a - * copy of the private key, so the caller remains responsible - * for destroying its copy after this function returns. - * - * certChain DER-encoded certs, client cert and its signers. - * Note: ssl takes this reference, and does not copy the chain. - * The caller should drop its reference without destroying the - * chain. SSL will free the chain when it is done with it. - * - * Return value: XXX - * - * XXX This code only works on the initial handshake on a connection, XXX - * It does not work on a subsequent handshake (redo). - * - * Caller holds 1stHandshakeLock. - */ -SECStatus -ssl3_RestartHandshakeAfterCertReq(sslSocket * ss, - CERTCertificate * cert, - SECKEYPrivateKey * key, - CERTCertificateList *certChain) -{ - SECStatus rv = SECSuccess; - - if (MSB(ss->version) == MSB(SSL_LIBRARY_VERSION_3_0)) { - /* XXX This code only works on the initial handshake on a connection, - ** XXX It does not work on a subsequent handshake (redo). - */ - if (ss->handshake != 0) { - ss->handshake = ssl_GatherRecord1stHandshake; - ss->ssl3.clientCertificate = cert; - ss->ssl3.clientCertChain = certChain; - if (key == NULL) { - (void)SSL3_SendAlert(ss, alert_warning, no_certificate); - ss->ssl3.clientPrivateKey = NULL; - } else { - ss->ssl3.clientPrivateKey = SECKEY_CopyPrivateKey(key); - } - ssl_GetRecvBufLock(ss); - if (ss->ssl3.hs.msgState.buf != NULL) { - rv = ssl3_HandleRecord(ss, NULL, &ss->gs.buf); - } - ssl_ReleaseRecvBufLock(ss); - } - } - return rv; -} - PRBool ssl3_CanFalseStart(sslSocket *ss) { PRBool rv; PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); + /* XXX: does not take into account whether we are waiting for + * SSL_RestartHandshakeAfterAuthCertificate or + * SSL_RestartHandshakeAfterCertReq. If/when that is done, this function + * could return different results each time it would be called. + */ + ssl_GetSpecReadLock(ss); rv = ss->opt.enableFalseStart && !ss->sec.isServer && !ss->ssl3.hs.isResuming && ss->ssl3.cwSpec && ss->ssl3.cwSpec->cipher_def->secret_key_size >= 10 && (ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_rsa || ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_dh || ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_ecdh); ssl_ReleaseSpecReadLock(ss); return rv; } +static SECStatus ssl3_SendClientSecondRound(sslSocket *ss); + /* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete * ssl3 Server Hello Done message. * Caller must hold Handshake and RecvBuf locks. */ static SECStatus ssl3_HandleServerHelloDone(sslSocket *ss) { SECStatus rv; SSL3WaitState ws = ss->ssl3.hs.ws; - PRBool send_verify = PR_FALSE; SSL_TRC(3, ("%d: SSL3[%d]: handle server_hello_done handshake", SSL_GETPID(), ss->fd)); PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); if (ws != wait_hello_done && ws != wait_server_cert && ws != wait_server_key && ws != wait_cert_request) { SSL3_SendAlert(ss, alert_fatal, unexpected_message); PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HELLO_DONE); return SECFailure; } + rv = ssl3_SendClientSecondRound(ss); + + return rv; +} + +/* Called from ssl3_HandleServerHelloDone and + * ssl3_RestartHandshakeAfterServerCert. + * + * Caller must hold Handshake and RecvBuf locks. + */ +static SECStatus +ssl3_SendClientSecondRound(sslSocket *ss) +{ + SECStatus rv; + PRBool sendClientCert; + + PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); + PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); + + sendClientCert = !ss->ssl3.sendEmptyCert && + ss->ssl3.clientCertChain != NULL && + ss->ssl3.clientPrivateKey != NULL; + + /* We must wait for the server's certificate to be authenticated before + * sending the client certificate in order to disclosing the client + * certificate to an attacker that does not have a valid cert for the + * domain we are connecting to. + * + * XXX: We should do the same for the NPN extension, but for that we + * need an option to give the application the ability to leak the NPN + * information to get better performance. + * + * During the initial handshake on a connection, we never send/receive + * application data until we have authenticated the server's certificate; + * i.e. we have fully authenticated the handshake before using the cipher + * specs agreed upon for that handshake. During a renegotiation, we may + * continue sending and receiving application data during the handshake + * interleaved with the handshake records. If we were to send the client's + * second round for a renegotiation before the server's certificate was + * authenticated, then the application data sent/received after this point + * would be using cipher spec that hadn't been authenticated. By waiting + * until the server's certificate has been authenticated during + * renegotiations, we ensure that renegotiations have the same property + * as initial handshakes; i.e. we have fully authenticated the handshake + * before using the cipher specs agreed upon for that handshake for + * application data. + */ + if (ss->ssl3.hs.restartTarget) { + PR_NOT_REACHED("unexpected ss->ssl3.hs.restartTarget"); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (ss->ssl3.hs.authCertificatePending && + (sendClientCert || ss->ssl3.sendEmptyCert || ss->firstHsDone)) { + ss->ssl3.hs.restartTarget = ssl3_SendClientSecondRound; + return SECWouldBlock; + } + ssl_GetXmitBufLock(ss); /*******************************/ if (ss->ssl3.sendEmptyCert) { ss->ssl3.sendEmptyCert = PR_FALSE; rv = ssl3_SendEmptyCertificate(ss); /* Don't send verify */ if (rv != SECSuccess) { goto loser; /* error code is set. */ } - } else - if (ss->ssl3.clientCertChain != NULL && - ss->ssl3.clientPrivateKey != NULL) { - send_verify = PR_TRUE; + } else if (sendClientCert) { rv = ssl3_SendCertificate(ss); if (rv != SECSuccess) { goto loser; /* error code is set. */ } } rv = ssl3_SendClientKeyExchange(ss); if (rv != SECSuccess) { goto loser; /* err is set. */ } - if (send_verify) { + if (sendClientCert) { rv = ssl3_SendCertificateVerify(ss); if (rv != SECSuccess) { goto loser; /* err is set. */ } } + rv = ssl3_SendChangeCipherSpecs(ss); if (rv != SECSuccess) { goto loser; /* err code was set. */ } + + /* XXX: If the server's certificate hasn't been authenticated by this + * point, then we may be leaking this NPN message to an attacker. + */ + if (!ss->firstHsDone) { + rv = ssl3_SendNextProto(ss); + if (rv != SECSuccess) { + goto loser; /* err code was set. */ + } + } + rv = ssl3_SendFinished(ss, 0); if (rv != SECSuccess) { goto loser; /* err code was set. */ } ssl_ReleaseXmitBufLock(ss); /*******************************/ if (ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn)) @@ -7804,19 +7817,16 @@ ssl3_CleanupPeerCerts(sslSocket *ss) * ssl3 Certificate message. * Caller must hold Handshake and RecvBuf locks. */ static SECStatus ssl3_HandleCertificate(sslSocket *ss, SSL3Opaque *b, PRUint32 length) { ssl3CertNode * c; ssl3CertNode * lastCert = NULL; - ssl3CertNode * certs = NULL; - PRArenaPool * arena = NULL; - CERTCertificate *cert; PRInt32 remaining = 0; PRInt32 size; SECStatus rv; PRBool isServer = (PRBool)(!!ss->sec.isServer); PRBool trusted = PR_FALSE; PRBool isTLS; SSL3AlertDescription desc = bad_certificate; int errCode = SSL_ERROR_RX_MALFORMED_CERTIFICATE; @@ -7863,21 +7873,21 @@ ssl3_HandleCertificate(sslSocket *ss, SS goto alert_loser; /* This is TLS's version of a no_certificate alert. */ /* I'm a server. I've requested a client cert. He hasn't got one. */ rv = ssl3_HandleNoCertificate(ss); if (rv != SECSuccess) { errCode = PORT_GetError(); goto loser; } - goto cert_block; - } - - ss->ssl3.peerCertArena = arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - if ( arena == NULL ) { + goto server_no_cert; + } + + ss->ssl3.peerCertArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (ss->ssl3.peerCertArena == NULL) { goto loser; /* don't send alerts on memory errors */ } /* First get the peer cert. */ remaining -= 3; if (remaining < 0) goto decode_loser; @@ -7917,17 +7927,17 @@ ssl3_HandleCertificate(sslSocket *ss, SS goto decode_loser; certItem.data = b; certItem.len = size; b += size; length -= size; remaining -= size; - c = PORT_ArenaNew(arena, ssl3CertNode); + c = PORT_ArenaNew(ss->ssl3.peerCertArena, ssl3CertNode); if (c == NULL) { goto loser; /* don't send alerts on memory errors */ } c->cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL, PR_FALSE, PR_TRUE); if (c->cert == NULL) { goto ambiguous_err; @@ -7935,70 +7945,62 @@ ssl3_HandleCertificate(sslSocket *ss, SS if (c->cert->trust) trusted = PR_TRUE; c->next = NULL; if (lastCert) { lastCert->next = c; } else { - certs = c; + ss->ssl3.peerCertChain = c; } lastCert = c; } if (remaining != 0) goto decode_loser; SECKEY_UpdateCertPQG(ss->sec.peerCert); + ss->ssl3.hs.authCertificatePending = PR_FALSE; + /* * Ask caller-supplied callback function to validate cert chain. */ rv = (SECStatus)(*ss->authCertificate)(ss->authCertificateArg, ss->fd, PR_TRUE, isServer); if (rv) { errCode = PORT_GetError(); - if (!ss->handleBadCert) { - goto bad_cert; - } - rv = (SECStatus)(*ss->handleBadCert)(ss->badCertArg, ss->fd); - if ( rv ) { - if ( rv == SECWouldBlock ) { - /* someone will handle this connection asynchronously*/ - SSL_DBG(("%d: SSL3[%d]: go to async cert handler", - SSL_GETPID(), ss->fd)); - ss->ssl3.peerCertChain = certs; - certs = NULL; - ssl_SetAlwaysBlock(ss); - goto cert_block; + if (rv != SECWouldBlock) { + if (ss->handleBadCert) { + rv = (*ss->handleBadCert)(ss->badCertArg, ss->fd); } - /* cert is bad */ + } + + if (rv == SECWouldBlock) { + if (ss->sec.isServer) { + errCode = SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS; + rv = SECFailure; + goto loser; + } + + ss->ssl3.hs.authCertificatePending = PR_TRUE; + rv = SECSuccess; + } + + if (rv != SECSuccess) { goto bad_cert; } - /* cert is good */ - } - - /* start SSL Step Up, if appropriate */ - cert = ss->sec.peerCert; - if (!isServer && - ssl3_global_policy_some_restricted && - ss->ssl3.policy == SSL_ALLOWED && - anyRestrictedEnabled(ss) && - SECSuccess == CERT_VerifyCertNow(cert->dbhandle, cert, - PR_FALSE, /* checkSig */ - certUsageSSLServerWithStepUp, -/*XXX*/ ss->authCertificateArg) ) { - ss->ssl3.policy = SSL_RESTRICTED; - ss->ssl3.hs.rehandshake = PR_TRUE; } ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert); if (!ss->sec.isServer) { + CERTCertificate *cert = ss->sec.peerCert; + /* set the server authentication and key exchange types and sizes ** from the value in the cert. If the key exchange key is different, ** it will get fixed when we handle the server key exchange message. */ SECKEYPublicKey * pubKey = CERT_ExtractPublicKey(cert); ss->sec.authAlgorithm = ss->ssl3.hs.kea_def->signKeyType; ss->sec.keaType = ss->ssl3.hs.kea_def->exchKeyType; if (pubKey) { @@ -8029,39 +8031,38 @@ ssl3_HandleCertificate(sslSocket *ss, SS * destroy pubKey and goto bad_cert */ } } #endif /* NSS_ENABLE_ECC */ SECKEY_DestroyPublicKey(pubKey); pubKey = NULL; } - } - - ss->ssl3.peerCertChain = certs; certs = NULL; arena = NULL; - -cert_block: - if (ss->sec.isServer) { - ss->ssl3.hs.ws = wait_client_key; - } else { + ss->ssl3.hs.ws = wait_cert_request; /* disallow server_key_exchange */ if (ss->ssl3.hs.kea_def->is_limited || /* XXX OR server cert is signing only. */ #ifdef NSS_ENABLE_ECC ss->ssl3.hs.kea_def->kea == kea_ecdhe_ecdsa || ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa || #endif /* NSS_ENABLE_ECC */ ss->ssl3.hs.kea_def->exchKeyType == kt_dh) { ss->ssl3.hs.ws = wait_server_key; /* allow server_key_exchange */ } - } - - /* rv must normally be equal to SECSuccess here. If we called - * handleBadCert, it can also be SECWouldBlock. - */ + } else { +server_no_cert: + ss->ssl3.hs.ws = wait_client_key; + } + + PORT_Assert(rv == SECSuccess); + if (rv != SECSuccess) { + errCode = SEC_ERROR_LIBRARY_FAILURE; + rv = SECFailure; + goto loser; + } return rv; ambiguous_err: errCode = PORT_GetError(); switch (errCode) { case PR_OUT_OF_MEMORY_ERROR: case SEC_ERROR_BAD_DATABASE: case SEC_ERROR_NO_MEMORY: @@ -8102,79 +8103,68 @@ bad_cert: /* caller has set errCode. */ decode_loser: desc = isTLS ? decode_error : bad_certificate; alert_loser: (void)SSL3_SendAlert(ss, alert_fatal, desc); loser: - ss->ssl3.peerCertChain = certs; certs = NULL; arena = NULL; ssl3_CleanupPeerCerts(ss); if (ss->sec.peerCert != NULL) { CERT_DestroyCertificate(ss->sec.peerCert); ss->sec.peerCert = NULL; } (void)ssl_MapLowLevelError(errCode); return SECFailure; } - -/* restart an SSL connection that we stopped to run certificate dialogs -** XXX Need to document here how an application marks a cert to show that -** the application has accepted it (overridden CERT_VerifyCert). - * - * XXX This code only works on the initial handshake on a connection, XXX - * It does not work on a subsequent handshake (redo). - * - * Return value: XXX - * - * Caller holds 1stHandshakeLock. +static SECStatus ssl3_FinishHandshake(sslSocket *ss); + +/* Caller must hold 1stHandshakeLock. */ -int -ssl3_RestartHandshakeAfterServerCert(sslSocket *ss) -{ - CERTCertificate * cert; - int rv = SECSuccess; - - if (MSB(ss->version) != MSB(SSL_LIBRARY_VERSION_3_0)) { - SET_ERROR_CODE - return SECFailure; - } - if (!ss->ssl3.initialized) { - SET_ERROR_CODE - return SECFailure; - } - - cert = ss->sec.peerCert; - - /* Permit step up if user decided to accept the cert */ - if (!ss->sec.isServer && - ssl3_global_policy_some_restricted && - ss->ssl3.policy == SSL_ALLOWED && - anyRestrictedEnabled(ss) && - (SECSuccess == CERT_VerifyCertNow(cert->dbhandle, cert, - PR_FALSE, /* checksig */ - certUsageSSLServerWithStepUp, -/*XXX*/ ss->authCertificateArg) )) { - ss->ssl3.policy = SSL_RESTRICTED; - ss->ssl3.hs.rehandshake = PR_TRUE; - } - - if (ss->handshake != NULL) { - ss->handshake = ssl_GatherRecord1stHandshake; - ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert); - - ssl_GetRecvBufLock(ss); - if (ss->ssl3.hs.msgState.buf != NULL) { - rv = ssl3_HandleRecord(ss, NULL, &ss->gs.buf); - } - ssl_ReleaseRecvBufLock(ss); - } +SECStatus +ssl3_RestartHandshakeAfterAuthCertificate(sslSocket *ss) +{ + SECStatus rv; + + PORT_Assert(ss->opt.noLocks || ssl_Have1stHandshakeLock(ss)); + + if (ss->sec.isServer) { + PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS); + return SECFailure; + } + + ssl_GetRecvBufLock(ss); + ssl_GetSSL3HandshakeLock(ss); + + if (!ss->ssl3.hs.authCertificatePending) { + PORT_SetError(PR_INVALID_STATE_ERROR); + rv = SECFailure; + } else { + ss->ssl3.hs.authCertificatePending = PR_FALSE; + if (ss->ssl3.hs.restartTarget != NULL) { + sslRestartTarget target = ss->ssl3.hs.restartTarget; + ss->ssl3.hs.restartTarget = NULL; + rv = target(ss); + /* Even if we blocked here, we have accomplished enough to claim + * success. Any remaining work will be taken care of by subsequent + * calls to SSL_ForceHandshake/PR_Send/PR_Read/etc. + */ + if (rv == SECWouldBlock) { + rv = SECSuccess; + } + } else { + rv = SECSuccess; + } + } + + ssl_ReleaseSSL3HandshakeLock(ss); + ssl_ReleaseRecvBufLock(ss); return rv; } static SECStatus ssl3_ComputeTLSFinished(ssl3CipherSpec *spec, PRBool isServer, const SSL3Finished * hashes, @@ -8216,16 +8206,50 @@ ssl3_ComputeTLSFinished(ssl3CipherSpec * rv = TLS_PRF(&spec->msItem, label, &inData, &outData, isFIPS); PORT_Assert(rv != SECSuccess || \ outData.len == sizeof tlsFinished->verify_data); } return rv; } /* called from ssl3_HandleServerHelloDone + */ +static SECStatus +ssl3_SendNextProto(sslSocket *ss) +{ + SECStatus rv; + int padding_len; + static const unsigned char padding[32] = {0}; + + if (ss->ssl3.nextProto.len == 0) + return SECSuccess; + + PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss)); + PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); + + padding_len = 32 - ((ss->ssl3.nextProto.len + 2) % 32); + + rv = ssl3_AppendHandshakeHeader(ss, next_proto, ss->ssl3.nextProto.len + + 2 + padding_len); + if (rv != SECSuccess) { + return rv; /* error code set by AppendHandshakeHeader */ + } + rv = ssl3_AppendHandshakeVariable(ss, ss->ssl3.nextProto.data, + ss->ssl3.nextProto.len, 1); + if (rv != SECSuccess) { + return rv; /* error code set by AppendHandshake */ + } + rv = ssl3_AppendHandshakeVariable(ss, padding, padding_len, 1); + if (rv != SECSuccess) { + return rv; /* error code set by AppendHandshake */ + } + return rv; +} + +/* called from ssl3_HandleServerHelloDone * ssl3_HandleClientHello * ssl3_HandleFinished */ static SECStatus ssl3_SendFinished(sslSocket *ss, PRInt32 flags) { ssl3CipherSpec *cwSpec; PRBool isTLS; @@ -8377,17 +8401,16 @@ ssl3_CacheWrappedMasterSecret(sslSocket static SECStatus ssl3_HandleFinished(sslSocket *ss, SSL3Opaque *b, PRUint32 length, const SSL3Hashes *hashes) { sslSessionID * sid = ss->sec.ci.sid; SECStatus rv = SECSuccess; PRBool isServer = ss->sec.isServer; PRBool isTLS; - PRBool doStepUp; SSL3KEAType effectiveExchKeyType; PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); SSL_TRC(3, ("%d: SSL3[%d]: handle finished handshake", SSL_GETPID(), ss->fd)); @@ -8433,18 +8456,16 @@ ssl3_HandleFinished(sslSocket *ss, SSL3O ss->ssl3.hs.finishedBytes = sizeof *hashes; if (0 != NSS_SecureMemcmp(hashes, b, length)) { (void)ssl3_HandshakeFailure(ss); PORT_SetError(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE); return SECFailure; } } - doStepUp = (PRBool)(!isServer && ss->ssl3.hs.rehandshake); - ssl_GetXmitBufLock(ss); /*************************************/ if ((isServer && !ss->ssl3.hs.isResuming) || (!isServer && ss->ssl3.hs.isResuming)) { PRInt32 flags = 0; /* Send a NewSessionTicket message if the client sent us * either an empty session ticket, or one that did not verify. @@ -8460,46 +8481,43 @@ ssl3_HandleFinished(sslSocket *ss, SSL3O } } rv = ssl3_SendChangeCipherSpecs(ss); if (rv != SECSuccess) { goto xmit_loser; /* err is set. */ } /* If this thread is in SSL_SecureSend (trying to write some data) - ** or if it is going to step up, ** then set the ssl_SEND_FLAG_FORCE_INTO_BUFFER flag, so that the ** last two handshake messages (change cipher spec and finished) ** will be sent in the same send/write call as the application data. */ - if (doStepUp || ss->writerThread == PR_GetCurrentThread()) { + if (ss->writerThread == PR_GetCurrentThread()) { flags = ssl_SEND_FLAG_FORCE_INTO_BUFFER; } + + if (!isServer && !ss->firstHsDone) { + rv = ssl3_SendNextProto(ss); + if (rv != SECSuccess) { + goto xmit_loser; /* err code was set. */ + } + } + rv = ssl3_SendFinished(ss, flags); if (rv != SECSuccess) { goto xmit_loser; /* err is set. */ } } - /* Optimization: don't cache this connection if we're going to step up. */ - if (doStepUp) { - ssl_FreeSID(sid); - ss->sec.ci.sid = sid = NULL; - ss->ssl3.hs.rehandshake = PR_FALSE; - rv = ssl3_SendClientHello(ss); xmit_loser: - ssl_ReleaseXmitBufLock(ss); - return rv; /* err code is set if appropriate. */ - } - ssl_ReleaseXmitBufLock(ss); /*************************************/ - - /* The first handshake is now completed. */ - ss->handshake = NULL; - ss->firstHsDone = PR_TRUE; + if (rv != SECSuccess) { + return rv; + } + ss->gs.writeOffset = 0; ss->gs.readOffset = 0; if (ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa) { effectiveExchKeyType = kt_rsa; } else { effectiveExchKeyType = ss->ssl3.hs.kea_def->exchKeyType; } @@ -8539,20 +8557,52 @@ xmit_loser: effectiveExchKeyType); sid->u.ssl3.keys.msIsWrapped = PR_TRUE; } ssl_ReleaseSpecReadLock(ss); /*************************************/ /* If the wrap failed, we don't cache the sid. * The connection continues normally however. */ - if (rv == SECSuccess) { - (*ss->sec.cache)(sid); - } - } + ss->ssl3.hs.cacheSID = rv == SECSuccess; + } + + if (ss->ssl3.hs.authCertificatePending) { + if (ss->ssl3.hs.restartTarget) { + PR_NOT_REACHED("ssl3_HandleFinished: unexpected restartTarget"); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + ss->ssl3.hs.restartTarget = ssl3_FinishHandshake; + return SECWouldBlock; + } + + rv = ssl3_FinishHandshake(ss); + return rv; +} + +SECStatus +ssl3_FinishHandshake(sslSocket * ss) +{ + SECStatus rv; + + PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); + PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); + PORT_Assert( ss->ssl3.hs.restartTarget == NULL ); + + /* The first handshake is now completed. */ + ss->handshake = NULL; + ss->firstHsDone = PR_TRUE; + + if (ss->sec.ci.sid->cached == never_cached && + !ss->opt.noCache && ss->sec.cache && ss->ssl3.hs.cacheSID) { + (*ss->sec.cache)(ss->sec.ci.sid); + } + ss->ssl3.hs.ws = idle_handshake; /* Do the handshake callback for sslv3 here, if we cannot false start. */ if (ss->handshakeCallback != NULL && !ssl3_CanFalseStart(ss)) { (ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData); } return SECSuccess; @@ -9201,17 +9251,16 @@ ssl3_InitState(sslSocket *ss) if (ss->ssl3.initialized) return SECSuccess; /* Function should be idempotent */ ss->ssl3.policy = SSL_ALLOWED; ssl_GetSpecWriteLock(ss); ss->ssl3.crSpec = ss->ssl3.cwSpec = &ss->ssl3.specs[0]; ss->ssl3.prSpec = ss->ssl3.pwSpec = &ss->ssl3.specs[1]; - ss->ssl3.hs.rehandshake = PR_FALSE; ss->ssl3.hs.sendingSCSV = PR_FALSE; ssl3_InitCipherSpec(ss, ss->ssl3.crSpec); ssl3_InitCipherSpec(ss, ss->ssl3.prSpec); ss->ssl3.hs.ws = (ss->sec.isServer) ? wait_client_hello : wait_server_hello; #ifdef NSS_ENABLE_ECC ss->ssl3.hs.negotiatedECCurves = SSL3_SUPPORTED_CURVES_MASK; #endif @@ -9310,20 +9359,16 @@ ssl3_SetPolicy(ssl3CipherSuite which, in ssl3CipherSuiteCfg *suite; suite = ssl_LookupCipherSuiteCfg(which, cipherSuites); if (suite == NULL) { return SECFailure; /* err code was set by ssl_LookupCipherSuiteCfg */ } suite->policy = policy; - if (policy == SSL_RESTRICTED) { - ssl3_global_policy_some_restricted = PR_TRUE; - } - return SECSuccess; } SECStatus ssl3_GetPolicy(ssl3CipherSuite which, PRInt32 *oPolicy) { ssl3CipherSuiteCfg *suite; PRInt32 policy; @@ -9535,11 +9580,13 @@ ssl3_DestroySSL3Info(sslSocket *ss) /* free the SSL3Buffer (msg_body) */ PORT_Free(ss->ssl3.hs.msg_body.buf); /* free up the CipherSpecs */ ssl3_DestroyCipherSpec(&ss->ssl3.specs[0], PR_TRUE/*freeSrvName*/); ssl3_DestroyCipherSpec(&ss->ssl3.specs[1], PR_TRUE/*freeSrvName*/); ss->ssl3.initialized = PR_FALSE; + + SECITEM_FreeItem(&ss->ssl3.nextProto, PR_FALSE); } /* End of ssl3con.c */
--- a/security/nss/lib/ssl/ssl3ext.c +++ b/security/nss/lib/ssl/ssl3ext.c @@ -36,17 +36,17 @@ * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* TLS extension code moved here from ssl3ecc.c */ -/* $Id: ssl3ext.c,v 1.16 2011/03/24 01:40:14 alexei.volkov.bugs%sun.com Exp $ */ +/* $Id: ssl3ext.c,v 1.20 2011/11/16 19:12:35 kaie%kuix.de Exp $ */ #include "nssrenam.h" #include "nss.h" #include "ssl.h" #include "sslproto.h" #include "sslimpl.h" #include "pk11pub.h" #include "blapi.h" @@ -73,16 +73,22 @@ static SECStatus ssl3_GetSessionTicketKe PK11SymKey **aes_key, PK11SymKey **mac_key); static SECStatus ssl3_GetSessionTicketKeys(const unsigned char **aes_key, PRUint32 *aes_key_length, const unsigned char **mac_key, PRUint32 *mac_key_length); static PRInt32 ssl3_SendRenegotiationInfoXtn(sslSocket * ss, PRBool append, PRUint32 maxBytes); static SECStatus ssl3_HandleRenegotiationInfoXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data); +static SECStatus ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, + PRUint16 ex_type, SECItem *data); +static SECStatus ssl3_ServerHandleNextProtoNegoXtn(sslSocket *ss, + PRUint16 ex_type, SECItem *data); +static PRInt32 ssl3_ClientSendNextProtoNegoXtn(sslSocket *ss, PRBool append, + PRUint32 maxBytes); /* * Write bytes. Using this function means the SECItem structure * cannot be freed. The caller is expected to call this function * on a shallow copy of the structure. */ static SECStatus ssl3_AppendToItem(SECItem *item, const unsigned char *buf, PRUint32 bytes) @@ -230,26 +236,28 @@ ssl3_GetSessionTicketKeys(const unsigned static const ssl3HelloExtensionHandler clientHelloHandlers[] = { { ssl_server_name_xtn, &ssl3_HandleServerNameXtn }, #ifdef NSS_ENABLE_ECC { ssl_elliptic_curves_xtn, &ssl3_HandleSupportedCurvesXtn }, { ssl_ec_point_formats_xtn, &ssl3_HandleSupportedPointFormatsXtn }, #endif { ssl_session_ticket_xtn, &ssl3_ServerHandleSessionTicketXtn }, { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn }, + { ssl_next_proto_neg_xtn, &ssl3_ServerHandleNextProtoNegoXtn }, { -1, NULL } }; /* These two tables are used by the client, to handle server hello * extensions. */ static const ssl3HelloExtensionHandler serverHelloHandlersTLS[] = { { ssl_server_name_xtn, &ssl3_HandleServerNameXtn }, /* TODO: add a handler for ssl_ec_point_formats_xtn */ { ssl_session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn }, { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn }, + { ssl_next_proto_neg_xtn, &ssl3_ClientHandleNextProtoNegoXtn }, { -1, NULL } }; static const ssl3HelloExtensionHandler serverHelloHandlersSSL3[] = { { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn }, { -1, NULL } }; @@ -262,17 +270,18 @@ static const ssl3HelloExtensionHandler s static const ssl3HelloExtensionSender clientHelloSendersTLS[SSL_MAX_EXTENSIONS] = { { ssl_server_name_xtn, &ssl3_SendServerNameXtn }, { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn }, #ifdef NSS_ENABLE_ECC { ssl_elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn }, { ssl_ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn }, #endif - { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn } + { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn }, + { ssl_next_proto_neg_xtn, &ssl3_ClientSendNextProtoNegoXtn } /* any extra entries will appear as { 0, NULL } */ }; static const ssl3HelloExtensionSender clientHelloSendersSSL3[SSL_MAX_EXTENSIONS] = { { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn } /* any extra entries will appear as { 0, NULL } */ }; @@ -529,16 +538,132 @@ ssl3_SendSessionTicketXtn( } return extension_length; loser: ss->xtnData.ticketTimestampVerified = PR_FALSE; return -1; } +/* handle an incoming Next Protocol Negotiation extension. */ +static SECStatus +ssl3_ServerHandleNextProtoNegoXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data) +{ + if (ss->firstHsDone || data->len != 0) { + /* Clients MUST send an empty NPN extension, if any. */ + PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID); + return SECFailure; + } + + return SECSuccess; +} + +/* ssl3_ValidateNextProtoNego checks that the given block of data is valid: none + * of the lengths may be 0 and the sum of the lengths must equal the length of + * the block. */ +SECStatus +ssl3_ValidateNextProtoNego(const unsigned char* data, unsigned int length) +{ + unsigned int offset = 0; + + while (offset < length) { + unsigned int newOffset = offset + 1 + (unsigned int) data[offset]; + /* Reject embedded nulls to protect against buggy applications that + * store protocol identifiers in null-terminated strings. + */ + if (newOffset > length || data[offset] == 0) { + PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID); + return SECFailure; + } + offset = newOffset; + } + + if (offset > length) { + PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID); + return SECFailure; + } + + return SECSuccess; +} + +static SECStatus +ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, PRUint16 ex_type, + SECItem *data) +{ + SECStatus rv; + unsigned char resultBuffer[255]; + SECItem result = { siBuffer, resultBuffer, 0 }; + + if (ss->firstHsDone) { + PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID); + return SECFailure; + } + + rv = ssl3_ValidateNextProtoNego(data->data, data->len); + if (rv != SECSuccess) + return rv; + + /* ss->nextProtoCallback cannot normally be NULL if we negotiated the + * extension. However, It is possible that an application erroneously + * cleared the callback between the time we sent the ClientHello and now. + */ + PORT_Assert(ss->nextProtoCallback != NULL); + if (!ss->nextProtoCallback) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + rv = ss->nextProtoCallback(ss->nextProtoArg, ss->fd, data->data, data->len, + result.data, &result.len, sizeof resultBuffer); + if (rv != SECSuccess) + return rv; + /* If the callback wrote more than allowed to |result| it has corrupted our + * stack. */ + if (result.len > sizeof result) { + PORT_SetError(SEC_ERROR_OUTPUT_LEN); + return SECFailure; + } + + SECITEM_FreeItem(&ss->ssl3.nextProto, PR_FALSE); + return SECITEM_CopyItem(NULL, &ss->ssl3.nextProto, &result); +} + +static PRInt32 +ssl3_ClientSendNextProtoNegoXtn(sslSocket * ss, PRBool append, + PRUint32 maxBytes) +{ + PRInt32 extension_length; + + /* Renegotiations do not send this extension. */ + if (!ss->nextProtoCallback || ss->firstHsDone) { + return 0; + } + + extension_length = 4; + + if (append && maxBytes >= extension_length) { + SECStatus rv; + rv = ssl3_AppendHandshakeNumber(ss, ssl_next_proto_neg_xtn, 2); + if (rv != SECSuccess) + goto loser; + rv = ssl3_AppendHandshakeNumber(ss, 0, 2); + if (rv != SECSuccess) + goto loser; + ss->xtnData.advertised[ss->xtnData.numAdvertised++] = + ssl_next_proto_neg_xtn; + } else if (maxBytes < extension_length) { + return 0; + } + + return extension_length; + +loser: + return -1; +} + /* * NewSessionTicket * Called from ssl3_HandleFinished */ SECStatus ssl3_SendNewSessionTicket(sslSocket *ss) { int i;
--- a/security/nss/lib/ssl/ssl3gthr.c +++ b/security/nss/lib/ssl/ssl3gthr.c @@ -187,31 +187,63 @@ int ssl3_GatherCompleteHandshake(sslSocket *ss, int flags) { SSL3Ciphertext cText; int rv; PRBool canFalseStart = PR_FALSE; PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); do { - /* bring in the next sslv3 record. */ - rv = ssl3_GatherData(ss, &ss->gs, flags); - if (rv <= 0) { - return rv; - } - - /* decipher it, and handle it if it's a handshake. - * If it's application data, ss->gs.buf will not be empty upon return. - * If it's a change cipher spec, alert, or handshake message, - * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess. - */ - cText.type = (SSL3ContentType)ss->gs.hdr[0]; - cText.version = (ss->gs.hdr[1] << 8) | ss->gs.hdr[2]; - cText.buf = &ss->gs.inbuf; - rv = ssl3_HandleRecord(ss, &cText, &ss->gs.buf); + /* Without this, we may end up wrongly reporting + * SSL_ERROR_RX_UNEXPECTED_* errors if we receive any records from the + * peer while we are waiting to be restarted. + */ + ssl_GetSSL3HandshakeLock(ss); + rv = ss->ssl3.hs.restartTarget == NULL ? SECSuccess : SECFailure; + ssl_ReleaseSSL3HandshakeLock(ss); + if (rv != SECSuccess) { + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return (int) SECFailure; + } + + /* Treat an empty msgState like a NULL msgState. (Most of the time + * when ssl3_HandleHandshake returns SECWouldBlock, it leaves + * behind a non-NULL but zero-length msgState). + * Test: async_cert_restart_server_sends_hello_request_first_in_separate_record + */ + if (ss->ssl3.hs.msgState.buf != NULL) { + if (ss->ssl3.hs.msgState.len == 0) { + ss->ssl3.hs.msgState.buf = NULL; + } + } + + if (ss->ssl3.hs.msgState.buf != NULL) { + /* ssl3_HandleHandshake previously returned SECWouldBlock and the + * as-yet-unprocessed plaintext of that previous handshake record. + * We need to process it now before we overwrite it with the next + * handshake record. + */ + rv = ssl3_HandleRecord(ss, NULL, &ss->gs.buf); + } else { + /* bring in the next sslv3 record. */ + rv = ssl3_GatherData(ss, &ss->gs, flags); + if (rv <= 0) { + return rv; + } + + /* decipher it, and handle it if it's a handshake. + * If it's application data, ss->gs.buf will not be empty upon return. + * If it's a change cipher spec, alert, or handshake message, + * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess. + */ + cText.type = (SSL3ContentType)ss->gs.hdr[0]; + cText.version = (ss->gs.hdr[1] << 8) | ss->gs.hdr[2]; + cText.buf = &ss->gs.inbuf; + rv = ssl3_HandleRecord(ss, &cText, &ss->gs.buf); + } if (rv < 0) { return ss->recvdCloseNotify ? 0 : rv; } /* If we kicked off a false start in ssl3_HandleServerHelloDone, break * out of this loop early without finishing the handshake. */ if (ss->opt.enableFalseStart) {
--- a/security/nss/lib/ssl/ssl3prot.h +++ b/security/nss/lib/ssl/ssl3prot.h @@ -33,17 +33,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: ssl3prot.h,v 1.19 2010/06/24 09:24:18 nelson%bolyard.com Exp $ */ +/* $Id: ssl3prot.h,v 1.20 2011/10/29 00:29:11 bsmith%mozilla.com Exp $ */ #ifndef __ssl3proto_h_ #define __ssl3proto_h_ typedef uint8 SSL3Opaque; typedef uint16 SSL3ProtocolVersion; /* version numbers are defined in sslproto.h */ @@ -152,17 +152,18 @@ typedef enum { server_hello = 2, new_session_ticket = 4, certificate = 11, server_key_exchange = 12, certificate_request = 13, server_hello_done = 14, certificate_verify = 15, client_key_exchange = 16, - finished = 20 + finished = 20, + next_proto = 67 } SSL3HandshakeType; typedef struct { uint8 empty; } SSL3HelloRequest; typedef struct { SSL3Opaque rand[SSL3_RANDOM_LENGTH];
--- a/security/nss/lib/ssl/sslcon.c +++ b/security/nss/lib/ssl/sslcon.c @@ -32,17 +32,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: sslcon.c,v 1.42 2011/08/01 07:08:09 kaie%kuix.de Exp $ */ +/* $Id: sslcon.c,v 1.45 2011/11/19 21:58:21 bsmith%mozilla.com Exp $ */ #include "nssrenam.h" #include "cert.h" #include "secitem.h" #include "sechash.h" #include "cryptohi.h" /* for SGN_ funcs */ #include "keyhi.h" /* for SECKEY_ high level functions. */ #include "ssl.h" @@ -513,17 +513,16 @@ ssl2_GetSendBuffer(sslSocket *ss, unsign /* Called from: * ssl2_ClientSetupSessionCypher() <- ssl2_HandleServerHelloMessage() * ssl2_HandleRequestCertificate() <- ssl2_HandleMessage() <- ssl_Do1stHandshake() * ssl2_HandleMessage() <- ssl_Do1stHandshake() * ssl2_HandleServerHelloMessage() <- ssl_Do1stHandshake() after ssl2_BeginClientHandshake() - * ssl2_RestartHandshakeAfterCertReq() <- Called from certdlgs.c in nav. * ssl2_HandleClientHelloMessage() <- ssl_Do1stHandshake() after ssl2_BeginServerHandshake() * * Acquires and releases the socket's xmitBufLock. */ int ssl2_SendErrorMessage(sslSocket *ss, int error) { @@ -760,17 +759,16 @@ ssl2_SendCertificateRequestMessage(sslSo sent = (*ss->sec.send)(ss, msg, sendLen, 0); rv = (sent >= 0) ? SECSuccess : (SECStatus)sent; done: ssl_ReleaseXmitBufLock(ss); /***************************************/ return rv; } /* Called from ssl2_HandleRequestCertificate() <- ssl2_HandleMessage() - * ssl2_RestartHandshakeAfterCertReq() <- (application) * Acquires and releases the socket's xmitBufLock. */ static int ssl2_SendCertificateResponseMessage(sslSocket *ss, SECItem *cert, SECItem *encCode) { PRUint8 *msg; int rv, sendLen; @@ -1172,17 +1170,16 @@ done: loser: ssl_ReleaseSpecReadLock(ss); return SECFailure; } /* ** Called from: ssl2_HandleServerHelloMessage, ** ssl2_HandleClientSessionKeyMessage, -** ssl2_RestartHandshakeAfterServerCert, ** ssl2_HandleClientHelloMessage, ** */ static void ssl2_UseEncryptedSendFunc(sslSocket *ss) { ssl_GetXmitBufLock(ss); PORT_Assert(ss->sec.hashcx != 0); @@ -1232,19 +1229,17 @@ ssl2_UseClearSendFunc(sslSocket *ss) * * This function is called from ssl_Do1stHandshake(). * The following functions put ssl_GatherRecord1stHandshake into ss->handshake: * ssl2_HandleMessage * ssl2_HandleVerifyMessage * ssl2_HandleServerHelloMessage * ssl2_BeginClientHandshake * ssl2_HandleClientSessionKeyMessage - * ssl2_RestartHandshakeAfterCertReq * ssl3_RestartHandshakeAfterCertReq - * ssl2_RestartHandshakeAfterServerCert * ssl3_RestartHandshakeAfterServerCert * ssl2_HandleClientHelloMessage * ssl2_BeginServerHandshake */ SECStatus ssl_GatherRecord1stHandshake(sslSocket *ss) { int rv; @@ -2227,18 +2222,16 @@ ssl2_TriggerNextMessage(sslSocket *ss) /* See if it's time to send our finished message, or if the handshakes are ** complete. Send finished message if appropriate. ** Returns SECSuccess unless anything goes wrong. ** ** Called from ssl2_HandleMessage, ** ssl2_HandleVerifyMessage ** ssl2_HandleServerHelloMessage ** ssl2_HandleClientSessionKeyMessage -** ssl2_RestartHandshakeAfterCertReq -** ssl2_RestartHandshakeAfterServerCert */ static SECStatus ssl2_TryToFinish(sslSocket *ss) { SECStatus rv; char e, ef; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); @@ -2262,17 +2255,16 @@ ssl2_TryToFinish(sslSocket *ss) return SECSuccess; } } return SECSuccess; } /* ** Called from ssl2_HandleRequestCertificate -** ssl2_RestartHandshakeAfterCertReq */ static SECStatus ssl2_SignResponse(sslSocket *ss, SECKEYPrivateKey *key, SECItem *response) { SGNContext * sgn = NULL; PRUint8 * challenge; @@ -2349,18 +2341,19 @@ ssl2_HandleRequestCertificate(sslSocket if (!ss->getClientAuthData) { SSL_TRC(7, ("%d: SSL[%d]: client doesn't support client-auth", SSL_GETPID(), ss->fd)); goto no_cert_error; } ret = (*ss->getClientAuthData)(ss->getClientAuthDataArg, ss->fd, NULL, &cert, &key); if ( ret == SECWouldBlock ) { - ssl_SetAlwaysBlock(ss); - goto done; + PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2); + ret = -1; + goto loser; } if (ret) { goto no_cert_error; } /* check what the callback function returned */ if ((!cert) || (!key)) { @@ -2710,18 +2703,17 @@ ssl2_HandleMessage(sslSocket *ss) loser: ssl_ReleaseRecvBufLock(ss); return SECFailure; } /************************************************************************/ -/* Called from ssl_Do1stHandshake, after ssl2_HandleServerHelloMessage or -** ssl2_RestartHandshakeAfterServerCert. +/* Called from ssl_Do1stHandshake, after ssl2_HandleServerHelloMessage. */ static SECStatus ssl2_HandleVerifyMessage(sslSocket *ss) { PRUint8 * data; SECStatus rv; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); @@ -2931,29 +2923,26 @@ ssl2_HandleServerHelloMessage(sslSocket /* verify the server's certificate. if sidHit, don't check signatures */ rv = (* ss->authCertificate)(ss->authCertificateArg, ss->fd, (PRBool)(!sidHit), PR_FALSE); if (rv) { if (ss->handleBadCert) { rv = (*ss->handleBadCert)(ss->badCertArg, ss->fd); if ( rv ) { if ( rv == SECWouldBlock ) { - /* someone will handle this connection asynchronously*/ - - SSL_DBG(("%d: SSL[%d]: go to async cert handler", - SSL_GETPID(), ss->fd)); - ssl_ReleaseRecvBufLock(ss); - ssl_SetAlwaysBlock(ss); - return SECWouldBlock; + SSL_DBG(("%d: SSL[%d]: SSL2 bad cert handler returned " + "SECWouldBlock", SSL_GETPID(), ss->fd)); + PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2); + rv = SECFailure; + } else { + /* cert is bad */ + SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d", + SSL_GETPID(), ss->fd, PORT_GetError())); } - /* cert is bad */ - SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d", - SSL_GETPID(), ss->fd, PORT_GetError())); goto loser; - } /* cert is good */ } else { SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d", SSL_GETPID(), ss->fd, PORT_GetError())); goto loser; } } @@ -3326,143 +3315,16 @@ bad_client: PORT_SetError(SSL_ERROR_BAD_CLIENT); /* FALLTHROUGH */ loser: return SECFailure; } /* - * attempt to restart the handshake after asynchronously handling - * a request for the client's certificate. - * - * inputs: - * cert Client cert chosen by application. - * key Private key associated with cert. - * - * XXX: need to make ssl2 and ssl3 versions of this function agree on whether - * they take the reference, or bump the ref count! - * - * Return value: XXX - * - * Caller holds 1stHandshakeLock. - */ -int -ssl2_RestartHandshakeAfterCertReq(sslSocket * ss, - CERTCertificate * cert, - SECKEYPrivateKey * key) -{ - int ret; - SECStatus rv = SECSuccess; - SECItem response; - - if (ss->version >= SSL_LIBRARY_VERSION_3_0) - return SECFailure; - - response.data = NULL; - - /* generate error if no cert or key */ - if ( ( cert == NULL ) || ( key == NULL ) ) { - goto no_cert; - } - - /* generate signed response to the challenge */ - rv = ssl2_SignResponse(ss, key, &response); - if ( rv != SECSuccess ) { - goto no_cert; - } - - /* Send response message */ - ret = ssl2_SendCertificateResponseMessage(ss, &cert->derCert, &response); - if (ret) { - goto no_cert; - } - - /* try to finish the handshake */ - ret = ssl2_TryToFinish(ss); - if (ret) { - goto loser; - } - - /* done with handshake */ - if (ss->handshake == 0) { - ret = SECSuccess; - goto done; - } - - /* continue handshake */ - ssl_GetRecvBufLock(ss); - ss->gs.recordLen = 0; - ssl_ReleaseRecvBufLock(ss); - - ss->handshake = ssl_GatherRecord1stHandshake; - ss->nextHandshake = ssl2_HandleMessage; - ret = ssl2_TriggerNextMessage(ss); - goto done; - -no_cert: - /* no cert - send error */ - ret = ssl2_SendErrorMessage(ss, SSL_PE_NO_CERTIFICATE); - goto done; - -loser: - ret = SECFailure; -done: - /* free allocated data */ - if ( response.data ) { - PORT_Free(response.data); - } - - return ret; -} - - -/* restart an SSL connection that we stopped to run certificate dialogs -** XXX Need to document here how an application marks a cert to show that -** the application has accepted it (overridden CERT_VerifyCert). - * - * Return value: XXX - * - * Caller holds 1stHandshakeLock. -*/ -int -ssl2_RestartHandshakeAfterServerCert(sslSocket *ss) -{ - int rv = SECSuccess; - - if (ss->version >= SSL_LIBRARY_VERSION_3_0) - return SECFailure; - - /* SSL 2 - ** At this point we have a completed session key and our session - ** cipher is setup and ready to go. Switch to encrypted write routine - ** as all future message data is to be encrypted. - */ - ssl2_UseEncryptedSendFunc(ss); - - rv = ssl2_TryToFinish(ss); - if (rv == SECSuccess && ss->handshake != NULL) { - /* handshake is not yet finished. */ - - SSL_TRC(5, ("%d: SSL[%d]: got server-hello, required=0x%d got=0x%x", - SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements, - ss->sec.ci.elements)); - - ssl_GetRecvBufLock(ss); - ss->gs.recordLen = 0; /* mark it all used up. */ - ssl_ReleaseRecvBufLock(ss); - - ss->handshake = ssl_GatherRecord1stHandshake; - ss->nextHandshake = ssl2_HandleVerifyMessage; - } - - return rv; -} - -/* ** Handle the initial hello message from the client ** ** not static because ssl2_GatherData() tests ss->nextHandshake for this value. */ SECStatus ssl2_HandleClientHelloMessage(sslSocket *ss) { sslSessionID *sid;
--- a/security/nss/lib/ssl/sslerr.h +++ b/security/nss/lib/ssl/sslerr.h @@ -31,17 +31,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: sslerr.h,v 1.14 2011/10/05 18:07:18 emaldona%redhat.com Exp $ */ +/* $Id: sslerr.h,v 1.18 2011/11/19 21:58:21 bsmith%mozilla.com Exp $ */ #ifndef __SSL_ERR_H_ #define __SSL_ERR_H_ #define SSL_ERROR_BASE (-0x3000) #define SSL_ERROR_LIMIT (SSL_ERROR_BASE + 1000) #define IS_SSL_ERROR(code) \ @@ -200,13 +200,19 @@ SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKE SSL_ERROR_DECOMPRESSION_FAILURE = (SSL_ERROR_BASE + 111), SSL_ERROR_RENEGOTIATION_NOT_ALLOWED = (SSL_ERROR_BASE + 112), SSL_ERROR_UNSAFE_NEGOTIATION = (SSL_ERROR_BASE + 113), SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD = (SSL_ERROR_BASE + 114), SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY = (SSL_ERROR_BASE + 115), +SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID = (SSL_ERROR_BASE + 116), + +SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2 = (SSL_ERROR_BASE + 117), +SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS = (SSL_ERROR_BASE + 118), +SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_CLIENTS = (SSL_ERROR_BASE + 119), + SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */ } SSLErrorCodes; #endif /* NO_SECURITY_ERROR_ENUM */ #endif /* __SSL_ERR_H_ */
--- a/security/nss/lib/ssl/sslimpl.h +++ b/security/nss/lib/ssl/sslimpl.h @@ -34,17 +34,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: sslimpl.h,v 1.84 2011/10/22 16:45:40 emaldona%redhat.com Exp $ */ +/* $Id: sslimpl.h,v 1.90 2011/11/19 21:58:21 bsmith%mozilla.com Exp $ */ #ifndef __sslimpl_h_ #define __sslimpl_h_ #ifdef DEBUG #undef NDEBUG #else #undef NDEBUG @@ -308,16 +308,20 @@ typedef struct { #ifdef NSS_ENABLE_ECC #define ssl_V3_SUITES_IMPLEMENTED 50 #else #define ssl_V3_SUITES_IMPLEMENTED 30 #endif /* NSS_ENABLE_ECC */ typedef struct sslOptionsStr { + /* If SSL_SetNextProtoNego has been called, then this contains the + * list of supported protocols. */ + SECItem nextProtoNego; + unsigned int useSecurity : 1; /* 1 */ unsigned int useSocks : 1; /* 2 */ unsigned int requestCertificate : 1; /* 3 */ unsigned int requireCertificate : 2; /* 4-5 */ unsigned int handshakeAsClient : 1; /* 6 */ unsigned int handshakeAsServer : 1; /* 7 */ unsigned int enableSSL2 : 1; /* 8 */ unsigned int enableSSL3 : 1; /* 9 */ @@ -741,16 +745,18 @@ struct TLSExtensionDataStr { /* SNI Extension related data * Names data is not coppied from the input buffer. It can not be * used outside the scope where input buffer is defined and that * is beyond ssl3_HandleClientHello function. */ SECItem *sniNameArr; PRUint32 sniNameArrSize; }; +typedef SECStatus (*sslRestartTarget)(sslSocket *); + /* ** This is the "hs" member of the "ssl3" struct. ** This entire struct is protected by ssl3HandshakeLock */ typedef struct SSL3HandshakeStateStr { SSL3Random server_random; SSL3Random client_random; SSL3WaitState ws; @@ -766,32 +772,37 @@ const ssl3CipherSuiteDef *suite_def; /* partial handshake message from record layer */ unsigned int header_bytes; /* number of bytes consumed from handshake */ /* message for message type and header length */ SSL3HandshakeType msg_type; unsigned long msg_len; SECItem ca_list; /* used only by client */ PRBool isResuming; /* are we resuming a session */ - PRBool rehandshake; /* immediately start another handshake - * when this one finishes */ PRBool usedStepDownKey; /* we did a server key exchange. */ PRBool sendingSCSV; /* instead of empty RI */ sslBuffer msgState; /* current state for handshake messages*/ /* protected by recvBufLock */ sslBuffer messages; /* Accumulated handshake messages */ PRUint16 finishedBytes; /* size of single finished below */ union { TLSFinished tFinished[2]; /* client, then server */ SSL3Hashes sFinished[2]; SSL3Opaque data[72]; } finishedMsgs; #ifdef NSS_ENABLE_ECC PRUint32 negotiatedECCurves; /* bit mask */ #endif /* NSS_ENABLE_ECC */ + + PRBool authCertificatePending; + /* Which function should SSL_RestartHandshake* call if we're blocked? + * One of NULL, ssl3_SendClientSecondRound, or ssl3_FinishHandshake. */ + sslRestartTarget restartTarget; + /* Shared state between ssl3_HandleFinished and ssl3_FinishHandshake */ + PRBool cacheSID; } SSL3HandshakeState; /* ** This is the "ssl3" struct, as in "ss->ssl3". ** note: ** usually, crSpec == cwSpec and prSpec == pwSpec. @@ -823,16 +834,22 @@ struct ssl3StateStr { /* These are used to keep track of the peer CA */ void * peerCertChain; /* chain while we are trying to validate it. */ CERTDistNames * ca_list; /* used by server. trusted CAs for this socket. */ PRBool initialized; SSL3HandshakeState hs; ssl3CipherSpec specs[2]; /* one is current, one is pending. */ + + /* In a client: if the server supports Next Protocol Negotiation, then + * this is the protocol that was negotiated. + */ + SECItem nextProto; + SSLNextProtoState nextProtoState; }; typedef struct { SSL3ContentType type; SSL3ProtocolVersion version; sslBuffer * buf; } SSL3Ciphertext; @@ -1054,16 +1071,18 @@ const unsigned char * preferredCipher; void *getClientAuthDataArg; SSLSNISocketConfig sniSocketConfig; void *sniSocketConfigArg; SSLBadCertHandler handleBadCert; void *badCertArg; SSLHandshakeCallback handshakeCallback; void *handshakeCallbackData; void *pkcs11PinArg; + SSLNextProtoCallback nextProtoCallback; + void *nextProtoArg; PRIntervalTime rTimeout; /* timeout for NSPR I/O */ PRIntervalTime wTimeout; /* timeout for NSPR I/O */ PRIntervalTime cTimeout; /* timeout for NSPR I/O */ PZLock * recvLock; /* lock against multiple reader threads. */ PZLock * sendLock; /* lock against multiple sender threads. */ @@ -1133,17 +1152,16 @@ const unsigned char * preferredCipher; extern NSSRWLock * ssl_global_data_lock; extern char ssl_debug; extern char ssl_trace; extern FILE * ssl_trace_iob; extern FILE * ssl_keylog_iob; extern CERTDistNames * ssl3_server_ca_list; extern PRUint32 ssl_sid_timeout; extern PRUint32 ssl3_sid_timeout; -extern PRBool ssl3_global_policy_some_restricted; extern const char * const ssl_cipherName[]; extern const char * const ssl3_cipherName[]; extern sslSessionIDLookupFunc ssl_sid_lookup; extern sslSessionIDCacheFunc ssl_sid_cache; extern sslSessionIDUncacheFunc ssl_sid_uncache; @@ -1247,17 +1265,17 @@ extern void ssl_FreeSID(sslSessionI extern int ssl3_SendApplicationData(sslSocket *ss, const PRUint8 *in, int len, int flags); extern PRBool ssl_FdIsBlocking(PRFileDesc *fd); extern PRBool ssl_SocketIsBlocking(sslSocket *ss); -extern void ssl_SetAlwaysBlock(sslSocket *ss); +extern void ssl3_SetAlwaysBlock(sslSocket *ss); extern SECStatus ssl_EnableNagleDelay(sslSocket *ss, PRBool enabled); extern PRBool ssl3_CanFalseStart(sslSocket *ss); #define SSL_LOCK_READER(ss) if (ss->recvLock) PZ_Lock(ss->recvLock) #define SSL_UNLOCK_READER(ss) if (ss->recvLock) PZ_Unlock(ss->recvLock) #define SSL_LOCK_WRITER(ss) if (ss->sendLock) PZ_Lock(ss->sendLock) @@ -1326,37 +1344,26 @@ extern SECStatus ssl3_KeyAndMacDeriveByp PRBool isTLS, PRBool isExport); extern SECStatus ssl3_MasterKeyDeriveBypass( ssl3CipherSpec * pwSpec, const unsigned char * cr, const unsigned char * sr, const SECItem * pms, PRBool isTLS, PRBool isRSA); /* These functions are called from secnav, even though they're "private". */ extern int ssl2_SendErrorMessage(struct sslSocketStr *ss, int error); -extern int SSL_RestartHandshakeAfterServerCert(struct sslSocketStr *ss); extern int SSL_RestartHandshakeAfterCertReq(struct sslSocketStr *ss, CERTCertificate *cert, SECKEYPrivateKey *key, CERTCertificateList *certChain); extern sslSocket *ssl_FindSocket(PRFileDesc *fd); extern void ssl_FreeSocket(struct sslSocketStr *ssl); extern SECStatus SSL3_SendAlert(sslSocket *ss, SSL3AlertLevel level, SSL3AlertDescription desc); -extern int ssl2_RestartHandshakeAfterCertReq(sslSocket * ss, - CERTCertificate * cert, - SECKEYPrivateKey * key); - -extern SECStatus ssl3_RestartHandshakeAfterCertReq(sslSocket * ss, - CERTCertificate * cert, - SECKEYPrivateKey * key, - CERTCertificateList *certChain); - -extern int ssl2_RestartHandshakeAfterServerCert(sslSocket *ss); -extern int ssl3_RestartHandshakeAfterServerCert(sslSocket *ss); +extern SECStatus ssl3_RestartHandshakeAfterAuthCertificate(sslSocket *ss); /* * for dealing with SSL 3.0 clients sending SSL 2.0 format hellos */ extern SECStatus ssl3_HandleV2ClientHello( sslSocket *ss, unsigned char *buffer, int length); extern SECStatus ssl3_StartHandshakeHash( sslSocket *ss, unsigned char *buf, int length); @@ -1564,16 +1571,19 @@ extern PRBool ssl_GetSessionTicketKeysPK SECKEYPublicKey *svrPubKey, void *pwArg, unsigned char *keyName, PK11SymKey **aesKey, PK11SymKey **macKey); /* Tell clients to consider tickets valid for this long. */ #define TLS_EX_SESS_TICKET_LIFETIME_HINT (2 * 24 * 60 * 60) /* 2 days */ #define TLS_EX_SESS_TICKET_VERSION (0x0100) +extern SECStatus ssl3_ValidateNextProtoNego(const unsigned char* data, + unsigned int length); + /* Construct a new NSPR socket for the app to use */ extern PRFileDesc *ssl_NewPRSocket(sslSocket *ss, PRFileDesc *fd); extern void ssl_FreePRSocket(PRFileDesc *fd); /* Internal config function so SSL2 can initialize the present state of * various ciphers */ extern int ssl3_config_match_init(sslSocket *);
--- a/security/nss/lib/ssl/sslsecur.c +++ b/security/nss/lib/ssl/sslsecur.c @@ -32,17 +32,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: sslsecur.c,v 1.49 2011/04/08 05:37:44 wtc%google.com Exp $ */ +/* $Id: sslsecur.c,v 1.53 2011/11/19 21:58:21 bsmith%mozilla.com Exp $ */ #include "cert.h" #include "secitem.h" #include "keyhi.h" #include "ssl.h" #include "sslimpl.h" #include "sslproto.h" #include "secoid.h" /* for SECOID_GetALgorithmTag */ #include "pk11func.h" /* for PK11_GenerateRandom */ @@ -168,30 +168,30 @@ ssl_Do1stHandshake(sslSocket *ss) return rv; } /* * Handshake function that blocks. Used to force a * retry on a connection on the next read/write. */ static SECStatus -AlwaysBlock(sslSocket *ss) +ssl3_AlwaysBlock(sslSocket *ss) { PORT_SetError(PR_WOULD_BLOCK_ERROR); /* perhaps redundant. */ return SECWouldBlock; } /* * set the initial handshake state machine to block */ void -ssl_SetAlwaysBlock(sslSocket *ss) +ssl3_SetAlwaysBlock(sslSocket *ss) { if (!ss->firstHsDone) { - ss->handshake = AlwaysBlock; + ss->handshake = ssl3_AlwaysBlock; ss->nextHandshake = 0; } } static SECStatus ssl_SetTimeout(PRFileDesc *fd, PRIntervalTime timeout) { sslSocket *ss; @@ -387,16 +387,28 @@ SSL_ForceHandshake(PRFileDesc *fd) SSL_GETPID(), fd)); return rv; } /* Don't waste my time */ if (!ss->opt.useSecurity) return SECSuccess; + if (!ssl_SocketIsBlocking(ss)) { + ssl_GetXmitBufLock(ss); + if (ss->pendingBuf.len != 0) { + rv = ssl_SendSavedWriteData(ss); + if ((rv < 0) && (PORT_GetError() != PR_WOULD_BLOCK_ERROR)) { + ssl_ReleaseXmitBufLock(ss); + return SECFailure; + } + } + ssl_ReleaseXmitBufLock(ss); + } + ssl_Get1stHandshakeLock(ss); if (ss->version >= SSL_LIBRARY_VERSION_3_0) { int gatherResult; ssl_GetRecvBufLock(ss); gatherResult = ssl3_GatherCompleteHandshake(ss, 0); ssl_ReleaseRecvBufLock(ss); @@ -1136,17 +1148,16 @@ ssl_SecureRecv(sslSocket *ss, unsigned c if (!ssl_SocketIsBlocking(ss) && !ss->opt.fdx) { ssl_GetXmitBufLock(ss); if (ss->pendingBuf.len != 0) { rv = ssl_SendSavedWriteData(ss); if ((rv < 0) && (PORT_GetError() != PR_WOULD_BLOCK_ERROR)) { ssl_ReleaseXmitBufLock(ss); return SECFailure; } - /* XXX short write? */ } ssl_ReleaseXmitBufLock(ss); } rv = 0; /* If any of these is non-zero, the initial handshake is not done. */ if (!ss->firstHsDone) { ssl_Get1stHandshakeLock(ss); @@ -1447,84 +1458,66 @@ SSL_CertDBHandleSet(PRFileDesc *fd, CERT if (!dbHandle) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } ss->dbHandle = dbHandle; return SECSuccess; } -/* - * attempt to restart the handshake after asynchronously handling - * a request for the client's certificate. - * - * inputs: - * cert Client cert chosen by application. - * Note: ssl takes this reference, and does not bump the - * reference count. The caller should drop its reference - * without calling CERT_DestroyCert after calling this function. - * - * key Private key associated with cert. This function makes a - * copy of the private key, so the caller remains responsible - * for destroying its copy after this function returns. - * - * certChain Chain of signers for cert. - * Note: ssl takes this reference, and does not copy the chain. - * The caller should drop its reference without destroying the - * chain. SSL will free the chain when it is done with it. - * - * Return value: XXX - * - * XXX This code only works on the initial handshake on a connection, XXX - * It does not work on a subsequent handshake (redo). +/* DO NOT USE. This function was exported in ssl.def with the wrong signature; + * this implementation exists to maintain link-time compatibility. */ int SSL_RestartHandshakeAfterCertReq(sslSocket * ss, CERTCertificate * cert, SECKEYPrivateKey * key, CERTCertificateList *certChain) { - int ret; - - ssl_Get1stHandshakeLock(ss); /************************************/ + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return -1; +} - if (ss->version >= SSL_LIBRARY_VERSION_3_0) { - ret = ssl3_RestartHandshakeAfterCertReq(ss, cert, key, certChain); - } else { - ret = ssl2_RestartHandshakeAfterCertReq(ss, cert, key); - } - - ssl_Release1stHandshakeLock(ss); /************************************/ - return ret; +/* DO NOT USE. This function was exported in ssl.def with the wrong signature; + * this implementation exists to maintain link-time compatibility. + */ +int +SSL_RestartHandshakeAfterServerCert(sslSocket * ss) +{ + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return -1; } +/* See documentation in ssl.h */ +SECStatus +SSL_RestartHandshakeAfterAuthCertificate(PRFileDesc *fd) +{ + SECStatus rv = SECSuccess; + sslSocket *ss = ssl_FindSocket(fd); -/* restart an SSL connection that we stopped to run certificate dialogs -** XXX Need to document here how an application marks a cert to show that -** the application has accepted it (overridden CERT_VerifyCert). - * - * XXX This code only works on the initial handshake on a connection, XXX - * It does not work on a subsequent handshake (redo). - * - * Return value: XXX -*/ -int -SSL_RestartHandshakeAfterServerCert(sslSocket *ss) -{ - int rv = SECSuccess; + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_RestartHandshakeAfterPeerCert", + SSL_GETPID(), fd)); + return SECFailure; + } + + ssl_Get1stHandshakeLock(ss); - ssl_Get1stHandshakeLock(ss); - - if (ss->version >= SSL_LIBRARY_VERSION_3_0) { - rv = ssl3_RestartHandshakeAfterServerCert(ss); + if (!ss->ssl3.initialized) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + rv = SECFailure; + } else if (ss->version < SSL_LIBRARY_VERSION_3_0) { + PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2); + rv = SECFailure; } else { - rv = ssl2_RestartHandshakeAfterServerCert(ss); + rv = ssl3_RestartHandshakeAfterAuthCertificate(ss); } ssl_Release1stHandshakeLock(ss); + return rv; } /* For more info see ssl.h */ SECStatus SSL_SNISocketConfigHook(PRFileDesc *fd, SSLSNISocketConfig func, void *arg) {
--- a/security/nss/lib/ssl/sslsock.c +++ b/security/nss/lib/ssl/sslsock.c @@ -35,17 +35,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: sslsock.c,v 1.75 2011/10/22 16:45:40 emaldona%redhat.com Exp $ */ +/* $Id: sslsock.c,v 1.80 2011/11/17 00:20:22 bsmith%mozilla.com Exp $ */ #include "seccomon.h" #include "cert.h" #include "keyhi.h" #include "ssl.h" #include "sslimpl.h" #include "sslproto.h" #include "nspr.h" #include "private/pprio.h" @@ -158,16 +158,17 @@ static const sslSocketOps ssl_secure_ops ssl_DefGetpeername, ssl_DefGetsockname }; /* ** default settings for socket enables */ static sslOptions ssl_defaults = { + { siBuffer, NULL, 0 }, /* nextProtoNego */ PR_TRUE, /* useSecurity */ PR_FALSE, /* useSocks */ PR_FALSE, /* requestCertificate */ 2, /* requireCertificate */ PR_FALSE, /* handshakeAsClient */ PR_FALSE, /* handshakeAsServer */ PR_FALSE, /* enableSSL2 */ /* now defaults to off in NSS 3.13 */ PR_TRUE, /* enableSSL3 */ @@ -435,16 +436,17 @@ ssl_DestroySocketContents(sslSocket *ss) if (ss->stepDownKeyPair) { ssl3_FreeKeyPair(ss->stepDownKeyPair); ss->stepDownKeyPair = NULL; } if (ss->ephemeralECDHKeyPair) { ssl3_FreeKeyPair(ss->ephemeralECDHKeyPair); ss->ephemeralECDHKeyPair = NULL; } + SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE); PORT_Assert(!ss->xtnData.sniNameArr); if (ss->xtnData.sniNameArr) { PORT_Free(ss->xtnData.sniNameArr); ss->xtnData.sniNameArr = NULL; } } /* @@ -1207,57 +1209,37 @@ SSL_CipherPrefGet(PRFileDesc *fd, PRInt3 rv = ssl3_CipherPrefGet(ss, (ssl3CipherSuite)which, enabled); } return rv; } SECStatus NSS_SetDomesticPolicy(void) { -#ifndef EXPORT_VERSION SECStatus status = SECSuccess; cipherPolicy * policy; for (policy = ssl_ciphers; policy->cipher != 0; ++policy) { status = SSL_SetPolicy(policy->cipher, SSL_ALLOWED); if (status != SECSuccess) break; } return status; -#else - return NSS_SetExportPolicy(); -#endif } SECStatus NSS_SetExportPolicy(void) { - SECStatus status = SECSuccess; - cipherPolicy * policy; - - for (policy = ssl_ciphers; policy->cipher != 0; ++policy) { - status = SSL_SetPolicy(policy->cipher, policy->export); - if (status != SECSuccess) - break; - } - return status; + return NSS_SetDomesticPolicy(); } SECStatus NSS_SetFrancePolicy(void) { - SECStatus status = SECSuccess; - cipherPolicy * policy; - - for (policy = ssl_ciphers; policy->cipher != 0; ++policy) { - status = SSL_SetPolicy(policy->cipher, policy->france); - if (status != SECSuccess) - break; - } - return status; + return NSS_SetDomesticPolicy(); } /* LOCKS ??? XXX */ PRFileDesc * SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd) { @@ -1296,16 +1278,155 @@ SSL_ImportFD(PRFileDesc *model, PRFileDe #endif ns = ssl_FindSocket(fd); PORT_Assert(ns); if (ns) ns->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ns, &addr)); return fd; } +SECStatus +SSL_SetNextProtoCallback(PRFileDesc *fd, SSLNextProtoCallback callback, + void *arg) +{ + sslSocket *ss = ssl_FindSocket(fd); + + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetNextProtoCallback", SSL_GETPID(), + fd)); + return SECFailure; + } + + ssl_GetSSL3HandshakeLock(ss); + ss->nextProtoCallback = callback; + ss->nextProtoArg = arg; + ssl_ReleaseSSL3HandshakeLock(ss); + + return SECSuccess; +} + +/* NextProtoStandardCallback is set as an NPN callback for the case when + * SSL_SetNextProtoNego is used. + */ +static SECStatus +ssl_NextProtoNegoCallback(void *arg, PRFileDesc *fd, + const unsigned char *protos, unsigned int protos_len, + unsigned char *protoOut, unsigned int *protoOutLen, + unsigned int protoMaxLen) +{ + unsigned int i, j; + const unsigned char *result; + sslSocket *ss = ssl_FindSocket(fd); + + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in ssl_NextProtoNegoCallback", + SSL_GETPID(), fd)); + return SECFailure; + } + + if (protos_len == 0) { + /* The server supports the extension, but doesn't have any protocols + * configured. In this case we request our favoured protocol. */ + goto pick_first; + } + + /* For each protocol in server preference, see if we support it. */ + for (i = 0; i < protos_len; ) { + for (j = 0; j < ss->opt.nextProtoNego.len; ) { + if (protos[i] == ss->opt.nextProtoNego.data[j] && + PORT_Memcmp(&protos[i+1], &ss->opt.nextProtoNego.data[j+1], + protos[i]) == 0) { + /* We found a match. */ + ss->ssl3.nextProtoState = SSL_NEXT_PROTO_NEGOTIATED; + result = &protos[i]; + goto found; + } + j += 1 + (unsigned int)ss->opt.nextProtoNego.data[j]; + } + i += 1 + (unsigned int)protos[i]; + } + +pick_first: + ss->ssl3.nextProtoState = SSL_NEXT_PROTO_NO_OVERLAP; + result = ss->opt.nextProtoNego.data; + +found: + *protoOutLen = result[0]; + if (protoMaxLen < result[0]) { + PORT_SetError(SEC_ERROR_OUTPUT_LEN); + return SECFailure; + } + memcpy(protoOut, result + 1, result[0]); + return SECSuccess; +} + +SECStatus +SSL_SetNextProtoNego(PRFileDesc *fd, const unsigned char *data, + unsigned int length) +{ + sslSocket *ss; + SECStatus rv; + SECItem dataItem = { siBuffer, (unsigned char *) data, length }; + + ss = ssl_FindSocket(fd); + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetNextProtoNego", + SSL_GETPID(), fd)); + return SECFailure; + } + + if (ssl3_ValidateNextProtoNego(data, length) != SECSuccess) + return SECFailure; + + ssl_GetSSL3HandshakeLock(ss); + SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE); + rv = SECITEM_CopyItem(NULL, &ss->opt.nextProtoNego, &dataItem); + ssl_ReleaseSSL3HandshakeLock(ss); + + if (rv != SECSuccess) + return rv; + + return SSL_SetNextProtoCallback(fd, ssl_NextProtoNegoCallback, NULL); +} + +SECStatus +SSL_GetNextProto(PRFileDesc *fd, SSLNextProtoState *state, unsigned char *buf, + unsigned int *bufLen, unsigned int bufLenMax) +{ + sslSocket *ss = ssl_FindSocket(fd); + + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetNextProto", SSL_GETPID(), + fd)); + return SECFailure; + } + + if (!state || !buf || !bufLen) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + *state = ss->ssl3.nextProtoState; + + if (ss->ssl3.nextProtoState != SSL_NEXT_PROTO_NO_SUPPORT && + ss->ssl3.nextProto.data) { + *bufLen = ss->ssl3.nextProto.len; + if (*bufLen > bufLenMax) { + PORT_SetError(SEC_ERROR_OUTPUT_LEN); + *bufLen = 0; + return SECFailure; + } + PORT_Memcpy(buf, ss->ssl3.nextProto.data, ss->ssl3.nextProto.len); + } else { + *bufLen = 0; + } + + return SECSuccess; +} + PRFileDesc * SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd) { sslSocket * sm = NULL, *ss = NULL; int i; sslServerCerts * mc = NULL; sslServerCerts * sc = NULL;
--- a/security/nss/lib/ssl/sslt.h +++ b/security/nss/lib/ssl/sslt.h @@ -32,17 +32,17 @@ * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -/* $Id: sslt.h,v 1.16 2010/02/04 03:21:11 wtc%google.com Exp $ */ +/* $Id: sslt.h,v 1.17 2011/10/29 00:29:11 bsmith%mozilla.com Exp $ */ #ifndef __sslt_h_ #define __sslt_h_ #include "prtypes.h" typedef struct SSL3StatisticsStr { /* statistics from ssl3_SendClientHello (sch) */ @@ -198,14 +198,15 @@ typedef enum { /* Update SSL_MAX_EXTENSIONS whenever a new extension type is added. */ typedef enum { ssl_server_name_xtn = 0, #ifdef NSS_ENABLE_ECC ssl_elliptic_curves_xtn = 10, ssl_ec_point_formats_xtn = 11, #endif ssl_session_ticket_xtn = 35, + ssl_next_proto_neg_xtn = 13172, ssl_renegotiation_info_xtn = 0xff01 /* experimental number */ } SSLExtensionType; -#define SSL_MAX_EXTENSIONS 5 +#define SSL_MAX_EXTENSIONS 6 #endif /* __sslt_h_ */
--- a/security/nss/lib/util/nssutil.h +++ b/security/nss/lib/util/nssutil.h @@ -46,22 +46,22 @@ /* * NSS utilities's major version, minor version, patch level, build number, * and whether this is a beta release. * * The format of the version string should be * "<major version>.<minor version>[.<patch level>[.<build number>]][ <Beta>]" */ -#define NSSUTIL_VERSION "3.13.1.0" +#define NSSUTIL_VERSION "3.13.2.0 Beta" #define NSSUTIL_VMAJOR 3 #define NSSUTIL_VMINOR 13 -#define NSSUTIL_VPATCH 1 +#define NSSUTIL_VPATCH 2 #define NSSUTIL_VBUILD 0 -#define NSSUTIL_BETA PR_FALSE +#define NSSUTIL_BETA PR_TRUE SEC_BEGIN_PROTOS /* * Returns a const string of the UTIL library version. */ extern const char *NSSUTIL_GetVersion(void);
--- a/security/nss/lib/util/pkcs11n.h +++ b/security/nss/lib/util/pkcs11n.h @@ -34,17 +34,17 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef _PKCS11N_H_ #define _PKCS11N_H_ #ifdef DEBUG -static const char CKT_CVS_ID[] = "@(#) $RCSfile: pkcs11n.h,v $ $Revision: 1.23 $ $Date: 2011/09/14 01:21:10 $"; +static const char CKT_CVS_ID[] = "@(#) $RCSfile: pkcs11n.h,v $ $Revision: 1.27 $ $Date: 2011/11/24 12:26:35 $"; #endif /* DEBUG */ /* * pkcs11n.h * * This file contains the NSS-specific type definitions for Cryptoki * (PKCS#11). */ @@ -157,17 +157,16 @@ static const char CKT_CVS_ID[] = "@(#) $ #define CKA_TRUST_IPSEC_USER (CKA_TRUST + 14) #define CKA_TRUST_TIME_STAMPING (CKA_TRUST + 15) #define CKA_TRUST_STEP_UP_APPROVED (CKA_TRUST + 16) #define CKA_CERT_SHA1_HASH (CKA_TRUST + 100) #define CKA_CERT_MD5_HASH (CKA_TRUST + 101) /* NSS trust stuff */ -/* XXX fgmr new ones here-- step-up, etc. */ /* HISTORICAL: define used to pass in the database key for DSA private keys */ #define CKA_NETSCAPE_DB 0xD5A0DB00L #define CKA_NETSCAPE_TRUST 0x80000001L /* FAKE PKCS #11 defines */ #define CKM_FAKE_RANDOM 0x80000efeUL #define CKM_INVALID_MECHANISM 0xffffffffUL @@ -341,33 +340,33 @@ typedef CK_ULONG CK_TRUST; #define CKT_NSS_VALID_DELEGATOR (CKT_NSS + 11) /* * old definitions. They still exist, but the plain meaning of the * labels have never been accurate to what was really implemented. * The new labels correctly reflect what the values effectively mean. */ -#if __GNUC__ > 3 +#if defined(__GNUC__) && (__GNUC__ > 3) /* make GCC warn when we use these #defines */ /* * This is really painful because GCC doesn't allow us to mark random * #defines as deprecated. We can only mark the following: * functions, variables, and types. * const variables will create extra storage for everyone including this * header file, so it's undesirable. * functions could be inlined to prevent storage creation, but will fail * when constant values are expected (like switch statements). * enum types do not seem to pay attention to the deprecated attribute. * * That leaves typedefs. We declare new types that we then deprecate, then * cast the resulting value to the deprecated type in the #define, thus * producting the warning when the #define is used. */ -#if (__GNUC__ == 4) && (__GNUC_MINOR < 5) +#if (__GNUC__ == 4) && (__GNUC_MINOR__ < 5) /* The mac doesn't like the friendlier deprecate messages. I'm assuming this * is a gcc version issue rather than mac or ppc specific */ typedef CK_TRUST __CKT_NSS_UNTRUSTED __attribute__((deprecated)); typedef CK_TRUST __CKT_NSS_VALID __attribute__ ((deprecated)); typedef CK_TRUST __CKT_NSS_MUST_VERIFY __attribute__((deprecated)); #else /* when possible, get a full deprecation warning. This works on gcc 4.5 * it may work on earlier versions of gcc */
--- a/security/nss/lib/util/secder.h +++ b/security/nss/lib/util/secder.h @@ -38,17 +38,17 @@ #define _SECDER_H_ #include "utilrename.h" /* * secder.h - public data structures and prototypes for the DER encoding and * decoding utilities library * - * $Id: secder.h,v 1.13 2008/06/18 01:04:23 wtc%google.com Exp $ + * $Id: secder.h,v 1.15 2011/11/16 19:12:36 kaie%kuix.de Exp $ */ #if defined(_WIN32_WCE) #else #include <time.h> #endif #include "plarena.h"
--- a/security/nss/lib/util/secoid.h +++ b/security/nss/lib/util/secoid.h @@ -37,17 +37,17 @@ #ifndef _SECOID_H_ #define _SECOID_H_ #include "utilrename.h" /* * secoid.h - public data structures and prototypes for ASN.1 OID functions * - * $Id: secoid.h,v 1.14 2009/11/11 23:24:33 alexei.volkov.bugs%sun.com Exp $ + * $Id: secoid.h,v 1.16 2011/11/16 19:12:36 kaie%kuix.de Exp $ */ #include "plarena.h" #include "seccomon.h" #include "secoidt.h" #include "secasn1t.h"
--- a/security/nss/tests/pkits/pkits.sh +++ b/security/nss/tests/pkits/pkits.sh @@ -122,17 +122,17 @@ pkits_init() echo "crls" $crls echo nss > ${PKITSdb}/pw ${BINDIR}/certutil -N -d ${PKITSdb} -f ${PKITSdb}/pw ${BINDIR}/certutil -A -n TrustAnchorRootCertificate -t "C,C,C" -i \ $certs/TrustAnchorRootCertificate.crt -d $PKITSdb if [ -z "$NSS_NO_PKITS_CRLS" ]; then - ${BINDIR}/crlutil -I -i $crls/TrustAnchorRootCRL.crl -d ${PKITSdb} + ${BINDIR}/crlutil -I -i $crls/TrustAnchorRootCRL.crl -d ${PKITSdb} -f ${PKITSdb}/pw else html "<H3>NO CRLs are being used.</H3>" pkits_log "NO CRLs are being used." fi cp ${PKITSdb}/* ${PKITSbkp} KNOWN_BUG= @@ -229,18 +229,18 @@ pkitsn() ################################ crlImport ############################# # local shell function to import a CRL, calls crlutil -I -i, writes # action and options to stdout ######################################################################## crlImport() { if [ -z "$NSS_NO_PKITS_CRLS" ]; then - echo "crlutil -d $PKITSdb -I -i $crls/$*" - ${BINDIR}/crlutil -d ${PKITSdb} -I -i $crls/$* > ${PKITSDIR}/cmdout.txt 2>&1 + echo "crlutil -d $PKITSdb -I -f ${PKITSdb}/pw -i $crls/$*" + ${BINDIR}/crlutil -d ${PKITSdb} -I -f ${PKITSdb}/pw -i $crls/$* > ${PKITSDIR}/cmdout.txt 2>&1 RET=$? cat ${PKITSDIR}/cmdout.txt if [ "$RET" -ne 0 ]; then html_failed "${VFY_ACTION} ($RET) " pkits_log "ERROR: ${VFY_ACTION} failed $RET" fi fi @@ -249,18 +249,18 @@ crlImport() ################################ crlImportn ############################# # local shell function to import an incorrect CRL, calls crlutil -I -i, # writes action and options to stdout ######################################################################## crlImportn() { RET=0 if [ -z "$NSS_NO_PKITS_CRLS" ]; then - echo "crlutil -d $PKITSdb -I -i $crls/$*" - ${BINDIR}/crlutil -d ${PKITSdb} -I -i $crls/$* > ${PKITSDIR}/cmdout.txt 2>&1 + echo "crlutil -d $PKITSdb -I -f ${PKITSdb}/pw -i $crls/$*" + ${BINDIR}/crlutil -d ${PKITSdb} -I -f ${PKITSdb}/pw -i $crls/$* > ${PKITSDIR}/cmdout.txt 2>&1 RET=$? cat ${PKITSDIR}/cmdout.txt if [ "$RET" -eq 0 ]; then html_failed "${VFY_ACTION} ($RET) " pkits_log "ERROR: ${VFY_ACTION} failed $RET" else html_passed "${VFY_ACTION} ($RET) "
--- a/security/nss/tests/ssl/ssl.sh +++ b/security/nss/tests/ssl/ssl.sh @@ -303,16 +303,26 @@ ssl_cov() exec < ${SSLCOV} while read ectype tls param testname do echo "${testname}" | grep "EXPORT" > /dev/null EXP=$? echo "${testname}" | grep "SSL2" > /dev/null SSL2=$? + + if [ "${SSL2}" -eq 0 ] ; then + # We cannot use asynchronous cert verification with SSL2 + SSL2_FLAGS=-O + else + # Do not enable SSL2 for non-SSL2-specific tests. SSL2 is disabled by + # default in libssl but it is enabled by default in tstclnt; we want + # to test the libssl default whenever possible. + SSL2_FLAGS=-2 + fi if [ "$NORM_EXT" = "Extended Test" -a "${SSL2}" -eq 0 ] ; then echo "$SCRIPTNAME: skipping $testname for $NORM_EXT" elif [ "$ectype" = "ECC" -a -z "$NSS_ENABLE_ECC" ] ; then echo "$SCRIPTNAME: skipping $testname (ECC only)" elif [ "$SERVER_MODE" = "fips" -o "$CLIENT_MODE" = "fips" ] && [ "$SSL2" -eq 0 -o "$EXP" -eq 0 ] ; then echo "$SCRIPTNAME: skipping $testname (non-FIPS only)" elif [ "`echo $ectype | cut -b 1`" != "#" ] ; then @@ -345,21 +355,21 @@ ssl_cov() is_selfserv_alive else kill_selfserv start_selfserv mixed=0 fi fi - echo "tstclnt -p ${PORT} -h ${HOSTADDR} -c ${param} ${TLS_FLAG} ${CLIENT_OPTIONS} \\" + echo "tstclnt -p ${PORT} -h ${HOSTADDR} -c ${param} ${TLS_FLAG} ${SSL2_FLAGS} ${CLIENT_OPTIONS} \\" echo " -f -d ${P_R_CLIENTDIR} -v -w nss < ${REQUEST_FILE}" rm ${TMP}/$HOST.tmp.$$ 2>/dev/null - ${PROFTOOL} ${BINDIR}/tstclnt -p ${PORT} -h ${HOSTADDR} -c ${param} ${TLS_FLAG} ${CLIENT_OPTIONS} -f \ + ${PROFTOOL} ${BINDIR}/tstclnt -p ${PORT} -h ${HOSTADDR} -c ${param} ${TLS_FLAG} ${SSL2_FLAGS} ${CLIENT_OPTIONS} -f \ -d ${P_R_CLIENTDIR} -v -w nss < ${REQUEST_FILE} \ >${TMP}/$HOST.tmp.$$ 2>&1 ret=$? cat ${TMP}/$HOST.tmp.$$ rm ${TMP}/$HOST.tmp.$$ 2>/dev/null html_msg $ret 0 "${testname}" \ "produced a returncode of $ret, expected is 0" fi
--- a/security/patches/README +++ b/security/patches/README @@ -1,2 +1,7 @@ This directory contains patches that were added locally on top of the NSS release. + +bug-542832-ssl-restart-4.patch and bug-542832-ssl-restart-tstclnt-4.patch were +added so that we could test the new PSM SSL threading code (bug 674147) and +SPDY (bug 528288). These patches will be removed when the NSS 3.13.2 release +that includes them is imported into mozilla-central.
new file mode 100644 --- /dev/null +++ b/security/patches/bug-542832-ssl-restart-4.patch @@ -0,0 +1,1076 @@ +Index: mozilla/security/nss/lib/ssl/SSLerrs.h +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/lib/ssl/SSLerrs.h,v +retrieving revision 1.15 +diff -u -8 -p -r1.15 SSLerrs.h +--- mozilla/security/nss/lib/ssl/SSLerrs.h 11 Nov 2011 19:06:51 -0000 1.15 ++++ mozilla/security/nss/lib/ssl/SSLerrs.h 16 Nov 2011 08:21:01 -0000 +@@ -406,8 +406,14 @@ ER3(SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED + ER3(SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY, (SSL_ERROR_BASE + 115), + "SSL received a weak ephemeral Diffie-Hellman key in Server Key Exchange handshake message.") + + ER3(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID, (SSL_ERROR_BASE + 116), + "SSL received invalid NPN extension data.") + + ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2, (SSL_ERROR_BASE + 117), + "SSL feature not supported for SSL 2.0 connections.") ++ ++ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS, (SSL_ERROR_BASE + 118), ++"SSL feature not supported for servers.") ++ ++ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_CLIENTS, (SSL_ERROR_BASE + 119), ++"SSL feature not supported for clients.") +Index: mozilla/security/nss/lib/ssl/ssl.def +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/lib/ssl/ssl.def,v +retrieving revision 1.27 +diff -u -8 -p -r1.27 ssl.def +--- mozilla/security/nss/lib/ssl/ssl.def 29 Oct 2011 00:29:11 -0000 1.27 ++++ mozilla/security/nss/lib/ssl/ssl.def 16 Nov 2011 08:21:01 -0000 +@@ -164,11 +164,12 @@ NSSSSL_GetVersion; + ;+ local: + ;+ *; + ;+}; + ;+NSS_3.13.2 { # NSS 3.13.2 release + ;+ global: + SSL_SetNextProtoCallback; + SSL_SetNextProtoNego; + SSL_GetNextProto; ++SSL_RestartHandshakeAfterAuthCertificate; + ;+ local: + ;+ *; + ;+}; +Index: mozilla/security/nss/lib/ssl/ssl.h +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/lib/ssl/ssl.h,v +retrieving revision 1.45 +diff -u -8 -p -r1.45 ssl.h +--- mozilla/security/nss/lib/ssl/ssl.h 29 Oct 2011 00:29:11 -0000 1.45 ++++ mozilla/security/nss/lib/ssl/ssl.h 16 Nov 2011 08:21:01 -0000 +@@ -334,16 +334,29 @@ SSL_IMPORT SECStatus SSL_SecurityStatus( + ** "fd" the socket "file" descriptor + */ + SSL_IMPORT CERTCertificate *SSL_PeerCertificate(PRFileDesc *fd); + + /* + ** Authenticate certificate hook. Called when a certificate comes in + ** (because of SSL_REQUIRE_CERTIFICATE in SSL_Enable) to authenticate the + ** certificate. ++** ++** The authenticate certificate hook must return SECSuccess to indicate the ++** certificate is valid, SECFailure to indicate the certificate is invalid, ++** or SECWouldBlock if the application will authenticate the certificate ++** asynchronously. ++** ++** If the authenticate certificate hook returns SECFailure, then the bad cert ++** hook will be called. The bad cert handler is NEVER called if the ++** authenticate certificate hook returns SECWouldBlock. ++** ++** See the documentation for SSL_RestartHandshakeAfterAuthCertificate for more ++** information about the asynchronous behavior that occurs when the ++** authenticate certificate hook returns SECWouldBlock. + */ + typedef SECStatus (PR_CALLBACK *SSLAuthCertificate)(void *arg, PRFileDesc *fd, + PRBool checkSig, + PRBool isServer); + + SSL_IMPORT SECStatus SSL_AuthCertificateHook(PRFileDesc *fd, + SSLAuthCertificate f, + void *arg); +@@ -437,16 +450,25 @@ SSL_IMPORT PRFileDesc *SSL_ReconfigFD(PR + * a - pkcs11 application specific data + */ + SSL_IMPORT SECStatus SSL_SetPKCS11PinArg(PRFileDesc *fd, void *a); + + /* + ** This is a callback for dealing with server certs that are not authenticated + ** by the client. The client app can decide that it actually likes the + ** cert by some external means and restart the connection. ++** ++** The bad cert hook must return SECSuccess to override the result of the ++** authenticate certificate hook, SECFailure if the certificate should still be ++** considered invalid, or SECWouldBlock if the application will authenticate ++** the certificate asynchronously. ++** ++** See the documentation for SSL_RestartHandshakeAfterAuthCertificate for more ++** information about the asynchronous behavior that occurs when the bad cert ++** hook returns SECWouldBlock. + */ + typedef SECStatus (PR_CALLBACK *SSLBadCertHandler)(void *arg, PRFileDesc *fd); + SSL_IMPORT SECStatus SSL_BadCertHook(PRFileDesc *fd, SSLBadCertHandler f, + void *arg); + + /* + ** Configure SSL socket for running a secure server. Needs the + ** certificate for the server and the servers private key. The arguments +@@ -735,11 +757,58 @@ SSL_IMPORT SECStatus SSL_HandshakeNegoti + */ + extern PRBool NSSSSL_VersionCheck(const char *importedVersion); + + /* + * Returns a const string of the SSL library version. + */ + extern const char *NSSSSL_GetVersion(void); + ++/* Restart an SSL connection that was paused to do asynchronous certificate ++ * chain validation (when the auth certificate hook or bad cert handler ++ * returned SECWouldBlock). ++ * ++ * Currently, this function works only for the client role of a connection; it ++ * does not work for the server role. ++ * ++ * The application MUST call SSL_RestartHandshakeAfterAuthCertificate after it ++ * has successfully validated the peer's certificate to continue the SSL ++ * handshake. ++ * ++ * The application MUST NOT call SSL_RestartHandshakeAfterAuthCertificate when ++ * certificate validation fails; instead, it should just close the connection. ++ * ++ * This function will not complete the entire handshake. The application must ++ * call SSL_ForceHandshake, PR_Recv, PR_Send, etc. after calling this function ++ * to force the handshake to complete. ++ * ++ * libssl will wait for the peer's certificate to be authenticated before ++ * calling the handshake callback, sending a client certificate, ++ * sending any application data, or returning any application data to the ++ * application (on the first handshake on a connection only). ++ * ++ * However, libssl may send and receive handshake messages while waiting for ++ * the application to call SSL_RestartHandshakeAfterAuthCertificate, and it may ++ * call other callbacks (e.g, the client auth data hook) before ++ * SSL_RestartHandshakeAfterAuthCertificate has been called. ++ * ++ * An application that uses this asynchronous mechanism will usually have lower ++ * handshake latency if it has to do public key operations on the certificate ++ * chain during the authentication, especially if it does so in parallel on ++ * another thread. However, if the application can authenticate the peer's ++ * certificate quickly then it may be more efficient to use the synchronous ++ * mechanism (i.e. returning SECFailure/SECSuccess instead of SECWouldBlock ++ * from the authenticate certificate hook). ++ * ++ * Be careful about converting an application from synchronous cert validation ++ * to asynchronous certificate validation. A naive conversion is likely to ++ * result in deadlocks; e.g. the application will wait in PR_Poll for network ++ * I/O on the connection while all network I/O on the connection is blocked ++ * waiting for this function to be called. ++ * ++ * Returns SECFailure on failure, SECSuccess on success. Never returns ++ * SECWouldBlock. ++ */ ++SSL_IMPORT SECStatus SSL_RestartHandshakeAfterAuthCertificate(PRFileDesc *fd); ++ + SEC_END_PROTOS + + #endif /* __ssl_h_ */ +Index: mozilla/security/nss/lib/ssl/ssl3con.c +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/lib/ssl/ssl3con.c,v +retrieving revision 1.155 +diff -u -8 -p -r1.155 ssl3con.c +--- mozilla/security/nss/lib/ssl/ssl3con.c 11 Nov 2011 19:06:52 -0000 1.155 ++++ mozilla/security/nss/lib/ssl/ssl3con.c 16 Nov 2011 08:21:02 -0000 +@@ -5644,153 +5644,161 @@ loser: + PORT_SetError(errCode); + rv = SECFailure; + done: + if (arena != NULL) + PORT_FreeArena(arena, PR_FALSE); + return rv; + } + +-/* +- * attempt to restart the handshake after asynchronously handling +- * a request for the client's certificate. +- * +- * inputs: +- * cert Client cert chosen by application. +- * Note: ssl takes this reference, and does not bump the +- * reference count. The caller should drop its reference +- * without calling CERT_DestroyCert after calling this function. +- * +- * key Private key associated with cert. This function makes a +- * copy of the private key, so the caller remains responsible +- * for destroying its copy after this function returns. +- * +- * certChain DER-encoded certs, client cert and its signers. +- * Note: ssl takes this reference, and does not copy the chain. +- * The caller should drop its reference without destroying the +- * chain. SSL will free the chain when it is done with it. +- * +- * Return value: XXX +- * +- * XXX This code only works on the initial handshake on a connection, XXX +- * It does not work on a subsequent handshake (redo). +- * +- * Caller holds 1stHandshakeLock. +- */ +-SECStatus +-ssl3_RestartHandshakeAfterCertReq(sslSocket * ss, +- CERTCertificate * cert, +- SECKEYPrivateKey * key, +- CERTCertificateList *certChain) +-{ +- SECStatus rv = SECSuccess; +- +- if (MSB(ss->version) == MSB(SSL_LIBRARY_VERSION_3_0)) { +- /* XXX This code only works on the initial handshake on a connection, +- ** XXX It does not work on a subsequent handshake (redo). +- */ +- if (ss->handshake != 0) { +- ss->handshake = ssl_GatherRecord1stHandshake; +- ss->ssl3.clientCertificate = cert; +- ss->ssl3.clientCertChain = certChain; +- if (key == NULL) { +- (void)SSL3_SendAlert(ss, alert_warning, no_certificate); +- ss->ssl3.clientPrivateKey = NULL; +- } else { +- ss->ssl3.clientPrivateKey = SECKEY_CopyPrivateKey(key); +- } +- ssl_GetRecvBufLock(ss); +- if (ss->ssl3.hs.msgState.buf != NULL) { +- rv = ssl3_HandleRecord(ss, NULL, &ss->gs.buf); +- } +- ssl_ReleaseRecvBufLock(ss); +- } +- } +- return rv; +-} +- + PRBool + ssl3_CanFalseStart(sslSocket *ss) { + PRBool rv; + + PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); + ++ /* XXX: does not take into account whether we are waiting for ++ * SSL_RestartHandshakeAfterAuthCertificate or ++ * SSL_RestartHandshakeAfterCertReq. If/when that is done, this function ++ * could return different results each time it would be called. ++ */ ++ + ssl_GetSpecReadLock(ss); + rv = ss->opt.enableFalseStart && + !ss->sec.isServer && + !ss->ssl3.hs.isResuming && + ss->ssl3.cwSpec && + ss->ssl3.cwSpec->cipher_def->secret_key_size >= 10 && + (ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_rsa || + ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_dh || + ss->ssl3.hs.kea_def->exchKeyType == ssl_kea_ecdh); + ssl_ReleaseSpecReadLock(ss); + return rv; + } + ++static SECStatus ssl3_SendClientSecondRound(sslSocket *ss); ++ + /* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete + * ssl3 Server Hello Done message. + * Caller must hold Handshake and RecvBuf locks. + */ + static SECStatus + ssl3_HandleServerHelloDone(sslSocket *ss) + { + SECStatus rv; + SSL3WaitState ws = ss->ssl3.hs.ws; +- PRBool send_verify = PR_FALSE; + + SSL_TRC(3, ("%d: SSL3[%d]: handle server_hello_done handshake", + SSL_GETPID(), ss->fd)); + PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); + PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); + + if (ws != wait_hello_done && + ws != wait_server_cert && + ws != wait_server_key && + ws != wait_cert_request) { + SSL3_SendAlert(ss, alert_fatal, unexpected_message); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HELLO_DONE); + return SECFailure; + } + ++ rv = ssl3_SendClientSecondRound(ss); ++ ++ return rv; ++} ++ ++/* Called from ssl3_HandleServerHelloDone and ++ * ssl3_RestartHandshakeAfterServerCert. ++ * ++ * Caller must hold Handshake and RecvBuf locks. ++ */ ++static SECStatus ++ssl3_SendClientSecondRound(sslSocket *ss) ++{ ++ SECStatus rv; ++ PRBool sendClientCert; ++ ++ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); ++ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); ++ ++ sendClientCert = !ss->ssl3.sendEmptyCert && ++ ss->ssl3.clientCertChain != NULL && ++ ss->ssl3.clientPrivateKey != NULL; ++ ++ /* We must wait for the server's certificate to be authenticated before ++ * sending the client certificate in order to disclosing the client ++ * certificate to an attacker that does not have a valid cert for the ++ * domain we are connecting to. ++ * ++ * XXX: We should do the same for the NPN extension, but for that we ++ * need an option to give the application the ability to leak the NPN ++ * information to get better performance. ++ * ++ * During the initial handshake on a connection, we never send/receive ++ * application data until we have authenticated the server's certificate; ++ * i.e. we have fully authenticated the handshake before using the cipher ++ * specs agreed upon for that handshake. During a renegotiation, we may ++ * continue sending and receiving application data during the handshake ++ * interleaved with the handshake records. If we were to send the client's ++ * second round for a renegotiation before the server's certificate was ++ * authenticated, then the application data sent/received after this point ++ * would be using cipher spec that hadn't been authenticated. By waiting ++ * until the server's certificate has been authenticated during ++ * renegotiations, we ensure that renegotiations have the same property ++ * as initial handshakes; i.e. we have fully authenticated the handshake ++ * before using the cipher specs agreed upon for that handshake for ++ * application data. ++ */ ++ if (ss->ssl3.hs.restartTarget) { ++ PR_NOT_REACHED("unexpected ss->ssl3.hs.restartTarget"); ++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); ++ return SECFailure; ++ } ++ if (ss->ssl3.hs.authCertificatePending && ++ (sendClientCert || ss->ssl3.sendEmptyCert || ss->firstHsDone)) { ++ ss->ssl3.hs.restartTarget = ssl3_SendClientSecondRound; ++ return SECWouldBlock; ++ } ++ + ssl_GetXmitBufLock(ss); /*******************************/ + + if (ss->ssl3.sendEmptyCert) { + ss->ssl3.sendEmptyCert = PR_FALSE; + rv = ssl3_SendEmptyCertificate(ss); + /* Don't send verify */ + if (rv != SECSuccess) { + goto loser; /* error code is set. */ + } +- } else +- if (ss->ssl3.clientCertChain != NULL && +- ss->ssl3.clientPrivateKey != NULL) { +- send_verify = PR_TRUE; ++ } else if (sendClientCert) { + rv = ssl3_SendCertificate(ss); + if (rv != SECSuccess) { + goto loser; /* error code is set. */ + } + } + + rv = ssl3_SendClientKeyExchange(ss); + if (rv != SECSuccess) { + goto loser; /* err is set. */ + } + +- if (send_verify) { ++ if (sendClientCert) { + rv = ssl3_SendCertificateVerify(ss); + if (rv != SECSuccess) { + goto loser; /* err is set. */ + } + } ++ + rv = ssl3_SendChangeCipherSpecs(ss); + if (rv != SECSuccess) { + goto loser; /* err code was set. */ + } + ++ /* XXX: If the server's certificate hasn't been authenticated by this ++ * point, then we may be leaking this NPN message to an attacker. ++ */ + if (!ss->firstHsDone) { + rv = ssl3_SendNextProto(ss); + if (rv != SECSuccess) { + goto loser; /* err code was set. */ + } + } + + rv = ssl3_SendFinished(ss, 0); +@@ -7809,18 +7817,16 @@ ssl3_CleanupPeerCerts(sslSocket *ss) + * ssl3 Certificate message. + * Caller must hold Handshake and RecvBuf locks. + */ + static SECStatus + ssl3_HandleCertificate(sslSocket *ss, SSL3Opaque *b, PRUint32 length) + { + ssl3CertNode * c; + ssl3CertNode * lastCert = NULL; +- ssl3CertNode * certs = NULL; +- PRArenaPool * arena = NULL; + PRInt32 remaining = 0; + PRInt32 size; + SECStatus rv; + PRBool isServer = (PRBool)(!!ss->sec.isServer); + PRBool trusted = PR_FALSE; + PRBool isTLS; + SSL3AlertDescription desc = bad_certificate; + int errCode = SSL_ERROR_RX_MALFORMED_CERTIFICATE; +@@ -7867,21 +7873,21 @@ ssl3_HandleCertificate(sslSocket *ss, SS + goto alert_loser; + /* This is TLS's version of a no_certificate alert. */ + /* I'm a server. I've requested a client cert. He hasn't got one. */ + rv = ssl3_HandleNoCertificate(ss); + if (rv != SECSuccess) { + errCode = PORT_GetError(); + goto loser; + } +- goto cert_block; ++ goto server_no_cert; + } + +- ss->ssl3.peerCertArena = arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); +- if ( arena == NULL ) { ++ ss->ssl3.peerCertArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); ++ if (ss->ssl3.peerCertArena == NULL) { + goto loser; /* don't send alerts on memory errors */ + } + + /* First get the peer cert. */ + remaining -= 3; + if (remaining < 0) + goto decode_loser; + +@@ -7921,17 +7927,17 @@ ssl3_HandleCertificate(sslSocket *ss, SS + goto decode_loser; + + certItem.data = b; + certItem.len = size; + b += size; + length -= size; + remaining -= size; + +- c = PORT_ArenaNew(arena, ssl3CertNode); ++ c = PORT_ArenaNew(ss->ssl3.peerCertArena, ssl3CertNode); + if (c == NULL) { + goto loser; /* don't send alerts on memory errors */ + } + + c->cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL, + PR_FALSE, PR_TRUE); + if (c->cert == NULL) { + goto ambiguous_err; +@@ -7939,51 +7945,55 @@ ssl3_HandleCertificate(sslSocket *ss, SS + + if (c->cert->trust) + trusted = PR_TRUE; + + c->next = NULL; + if (lastCert) { + lastCert->next = c; + } else { +- certs = c; ++ ss->ssl3.peerCertChain = c; + } + lastCert = c; + } + + if (remaining != 0) + goto decode_loser; + + SECKEY_UpdateCertPQG(ss->sec.peerCert); + ++ ss->ssl3.hs.authCertificatePending = PR_FALSE; ++ + /* + * Ask caller-supplied callback function to validate cert chain. + */ + rv = (SECStatus)(*ss->authCertificate)(ss->authCertificateArg, ss->fd, + PR_TRUE, isServer); + if (rv) { + errCode = PORT_GetError(); +- if (!ss->handleBadCert) { +- goto bad_cert; ++ if (rv != SECWouldBlock) { ++ if (ss->handleBadCert) { ++ rv = (*ss->handleBadCert)(ss->badCertArg, ss->fd); ++ } + } +- rv = (SECStatus)(*ss->handleBadCert)(ss->badCertArg, ss->fd); +- if ( rv ) { +- if ( rv == SECWouldBlock ) { +- /* someone will handle this connection asynchronously*/ +- SSL_DBG(("%d: SSL3[%d]: go to async cert handler", +- SSL_GETPID(), ss->fd)); +- ss->ssl3.peerCertChain = certs; +- certs = NULL; +- ssl3_SetAlwaysBlock(ss); +- goto cert_block; ++ ++ if (rv == SECWouldBlock) { ++ if (ss->sec.isServer) { ++ errCode = SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS; ++ rv = SECFailure; ++ goto loser; + } +- /* cert is bad */ ++ ++ ss->ssl3.hs.authCertificatePending = PR_TRUE; ++ rv = SECSuccess; ++ } ++ ++ if (rv != SECSuccess) { + goto bad_cert; + } +- /* cert is good */ + } + + ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert); + + if (!ss->sec.isServer) { + CERTCertificate *cert = ss->sec.peerCert; + + /* set the server authentication and key exchange types and sizes +@@ -8021,39 +8031,38 @@ ssl3_HandleCertificate(sslSocket *ss, SS + * destroy pubKey and goto bad_cert + */ + } + } + #endif /* NSS_ENABLE_ECC */ + SECKEY_DestroyPublicKey(pubKey); + pubKey = NULL; + } +- } + +- ss->ssl3.peerCertChain = certs; certs = NULL; arena = NULL; +- +-cert_block: +- if (ss->sec.isServer) { +- ss->ssl3.hs.ws = wait_client_key; +- } else { + ss->ssl3.hs.ws = wait_cert_request; /* disallow server_key_exchange */ + if (ss->ssl3.hs.kea_def->is_limited || + /* XXX OR server cert is signing only. */ + #ifdef NSS_ENABLE_ECC + ss->ssl3.hs.kea_def->kea == kea_ecdhe_ecdsa || + ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa || + #endif /* NSS_ENABLE_ECC */ + ss->ssl3.hs.kea_def->exchKeyType == kt_dh) { + ss->ssl3.hs.ws = wait_server_key; /* allow server_key_exchange */ + } ++ } else { ++server_no_cert: ++ ss->ssl3.hs.ws = wait_client_key; + } + +- /* rv must normally be equal to SECSuccess here. If we called +- * handleBadCert, it can also be SECWouldBlock. +- */ ++ PORT_Assert(rv == SECSuccess); ++ if (rv != SECSuccess) { ++ errCode = SEC_ERROR_LIBRARY_FAILURE; ++ rv = SECFailure; ++ goto loser; ++ } + return rv; + + ambiguous_err: + errCode = PORT_GetError(); + switch (errCode) { + case PR_OUT_OF_MEMORY_ERROR: + case SEC_ERROR_BAD_DATABASE: + case SEC_ERROR_NO_MEMORY: +@@ -8094,64 +8103,69 @@ bad_cert: /* caller has set errCode. */ + + decode_loser: + desc = isTLS ? decode_error : bad_certificate; + + alert_loser: + (void)SSL3_SendAlert(ss, alert_fatal, desc); + + loser: +- ss->ssl3.peerCertChain = certs; certs = NULL; arena = NULL; + ssl3_CleanupPeerCerts(ss); + + if (ss->sec.peerCert != NULL) { + CERT_DestroyCertificate(ss->sec.peerCert); + ss->sec.peerCert = NULL; + } + (void)ssl_MapLowLevelError(errCode); + return SECFailure; + } + ++static SECStatus ssl3_FinishHandshake(sslSocket *ss); + +-/* restart an SSL connection that we stopped to run certificate dialogs +-** XXX Need to document here how an application marks a cert to show that +-** the application has accepted it (overridden CERT_VerifyCert). +- * +- * XXX This code only works on the initial handshake on a connection, XXX +- * It does not work on a subsequent handshake (redo). +- * +- * Return value: XXX +- * +- * Caller holds 1stHandshakeLock. ++/* Caller must hold 1stHandshakeLock. + */ +-int +-ssl3_RestartHandshakeAfterServerCert(sslSocket *ss) ++SECStatus ++ssl3_RestartHandshakeAfterAuthCertificate(sslSocket *ss) + { +- int rv = SECSuccess; ++ SECStatus rv; + +- if (MSB(ss->version) != MSB(SSL_LIBRARY_VERSION_3_0)) { +- SET_ERROR_CODE +- return SECFailure; +- } +- if (!ss->ssl3.initialized) { +- SET_ERROR_CODE +- return SECFailure; ++ PORT_Assert(ss->opt.noLocks || ssl_Have1stHandshakeLock(ss)); ++ ++ if (ss->sec.isServer) { ++ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS); ++ return SECFailure; + } + +- if (ss->handshake != NULL) { +- ss->handshake = ssl_GatherRecord1stHandshake; +- ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert); ++ ssl_GetRecvBufLock(ss); ++ ssl_GetSSL3HandshakeLock(ss); + +- ssl_GetRecvBufLock(ss); +- if (ss->ssl3.hs.msgState.buf != NULL) { +- rv = ssl3_HandleRecord(ss, NULL, &ss->gs.buf); +- } +- ssl_ReleaseRecvBufLock(ss); ++ if (!ss->ssl3.hs.authCertificatePending) { ++ PORT_SetError(PR_INVALID_STATE_ERROR); ++ rv = SECFailure; ++ } else { ++ ss->ssl3.hs.authCertificatePending = PR_FALSE; ++ if (ss->ssl3.hs.restartTarget != NULL) { ++ sslRestartTarget target = ss->ssl3.hs.restartTarget; ++ ss->ssl3.hs.restartTarget = NULL; ++ rv = target(ss); ++ /* Even if we blocked here, we have accomplished enough to claim ++ * success. Any remaining work will be taken care of by subsequent ++ * calls to SSL_ForceHandshake/PR_Send/PR_Read/etc. ++ */ ++ if (rv == SECWouldBlock) { ++ rv = SECSuccess; ++ } ++ } else { ++ rv = SECSuccess; ++ } + } + ++ ssl_ReleaseSSL3HandshakeLock(ss); ++ ssl_ReleaseRecvBufLock(ss); ++ + return rv; + } + + static SECStatus + ssl3_ComputeTLSFinished(ssl3CipherSpec *spec, + PRBool isServer, + const SSL3Finished * hashes, + TLSFinished * tlsFinished) +@@ -8494,19 +8508,16 @@ ssl3_HandleFinished(sslSocket *ss, SSL3O + } + + xmit_loser: + ssl_ReleaseXmitBufLock(ss); /*************************************/ + if (rv != SECSuccess) { + return rv; + } + +- /* The first handshake is now completed. */ +- ss->handshake = NULL; +- ss->firstHsDone = PR_TRUE; + ss->gs.writeOffset = 0; + ss->gs.readOffset = 0; + + if (ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa) { + effectiveExchKeyType = kt_rsa; + } else { + effectiveExchKeyType = ss->ssl3.hs.kea_def->exchKeyType; + } +@@ -8546,20 +8557,52 @@ xmit_loser: + effectiveExchKeyType); + sid->u.ssl3.keys.msIsWrapped = PR_TRUE; + } + ssl_ReleaseSpecReadLock(ss); /*************************************/ + + /* If the wrap failed, we don't cache the sid. + * The connection continues normally however. + */ +- if (rv == SECSuccess) { +- (*ss->sec.cache)(sid); +- } ++ ss->ssl3.hs.cacheSID = rv == SECSuccess; + } ++ ++ if (ss->ssl3.hs.authCertificatePending) { ++ if (ss->ssl3.hs.restartTarget) { ++ PR_NOT_REACHED("ssl3_HandleFinished: unexpected restartTarget"); ++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); ++ return SECFailure; ++ } ++ ++ ss->ssl3.hs.restartTarget = ssl3_FinishHandshake; ++ return SECWouldBlock; ++ } ++ ++ rv = ssl3_FinishHandshake(ss); ++ return rv; ++} ++ ++SECStatus ++ssl3_FinishHandshake(sslSocket * ss) ++{ ++ SECStatus rv; ++ ++ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); ++ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); ++ PORT_Assert( ss->ssl3.hs.restartTarget == NULL ); ++ ++ /* The first handshake is now completed. */ ++ ss->handshake = NULL; ++ ss->firstHsDone = PR_TRUE; ++ ++ if (ss->sec.ci.sid->cached == never_cached && ++ !ss->opt.noCache && ss->sec.cache && ss->ssl3.hs.cacheSID) { ++ (*ss->sec.cache)(ss->sec.ci.sid); ++ } ++ + ss->ssl3.hs.ws = idle_handshake; + + /* Do the handshake callback for sslv3 here, if we cannot false start. */ + if (ss->handshakeCallback != NULL && !ssl3_CanFalseStart(ss)) { + (ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData); + } + + return SECSuccess; +Index: mozilla/security/nss/lib/ssl/ssl3gthr.c +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/lib/ssl/ssl3gthr.c,v +retrieving revision 1.10 +diff -u -8 -p -r1.10 ssl3gthr.c +--- mozilla/security/nss/lib/ssl/ssl3gthr.c 30 Jul 2010 03:00:17 -0000 1.10 ++++ mozilla/security/nss/lib/ssl/ssl3gthr.c 16 Nov 2011 08:21:02 -0000 +@@ -187,31 +187,63 @@ int + ssl3_GatherCompleteHandshake(sslSocket *ss, int flags) + { + SSL3Ciphertext cText; + int rv; + PRBool canFalseStart = PR_FALSE; + + PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); + do { +- /* bring in the next sslv3 record. */ +- rv = ssl3_GatherData(ss, &ss->gs, flags); +- if (rv <= 0) { +- return rv; +- } +- +- /* decipher it, and handle it if it's a handshake. +- * If it's application data, ss->gs.buf will not be empty upon return. +- * If it's a change cipher spec, alert, or handshake message, +- * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess. +- */ +- cText.type = (SSL3ContentType)ss->gs.hdr[0]; +- cText.version = (ss->gs.hdr[1] << 8) | ss->gs.hdr[2]; +- cText.buf = &ss->gs.inbuf; +- rv = ssl3_HandleRecord(ss, &cText, &ss->gs.buf); ++ /* Without this, we may end up wrongly reporting ++ * SSL_ERROR_RX_UNEXPECTED_* errors if we receive any records from the ++ * peer while we are waiting to be restarted. ++ */ ++ ssl_GetSSL3HandshakeLock(ss); ++ rv = ss->ssl3.hs.restartTarget == NULL ? SECSuccess : SECFailure; ++ ssl_ReleaseSSL3HandshakeLock(ss); ++ if (rv != SECSuccess) { ++ PORT_SetError(PR_WOULD_BLOCK_ERROR); ++ return (int) SECFailure; ++ } ++ ++ /* Treat an empty msgState like a NULL msgState. (Most of the time ++ * when ssl3_HandleHandshake returns SECWouldBlock, it leaves ++ * behind a non-NULL but zero-length msgState). ++ * Test: async_cert_restart_server_sends_hello_request_first_in_separate_record ++ */ ++ if (ss->ssl3.hs.msgState.buf != NULL) { ++ if (ss->ssl3.hs.msgState.len == 0) { ++ ss->ssl3.hs.msgState.buf = NULL; ++ } ++ } ++ ++ if (ss->ssl3.hs.msgState.buf != NULL) { ++ /* ssl3_HandleHandshake previously returned SECWouldBlock and the ++ * as-yet-unprocessed plaintext of that previous handshake record. ++ * We need to process it now before we overwrite it with the next ++ * handshake record. ++ */ ++ rv = ssl3_HandleRecord(ss, NULL, &ss->gs.buf); ++ } else { ++ /* bring in the next sslv3 record. */ ++ rv = ssl3_GatherData(ss, &ss->gs, flags); ++ if (rv <= 0) { ++ return rv; ++ } ++ ++ /* decipher it, and handle it if it's a handshake. ++ * If it's application data, ss->gs.buf will not be empty upon return. ++ * If it's a change cipher spec, alert, or handshake message, ++ * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess. ++ */ ++ cText.type = (SSL3ContentType)ss->gs.hdr[0]; ++ cText.version = (ss->gs.hdr[1] << 8) | ss->gs.hdr[2]; ++ cText.buf = &ss->gs.inbuf; ++ rv = ssl3_HandleRecord(ss, &cText, &ss->gs.buf); ++ } + if (rv < 0) { + return ss->recvdCloseNotify ? 0 : rv; + } + + /* If we kicked off a false start in ssl3_HandleServerHelloDone, break + * out of this loop early without finishing the handshake. + */ + if (ss->opt.enableFalseStart) { +Index: mozilla/security/nss/lib/ssl/sslerr.h +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/lib/ssl/sslerr.h,v +retrieving revision 1.16 +diff -u -8 -p -r1.16 sslerr.h +--- mozilla/security/nss/lib/ssl/sslerr.h 11 Nov 2011 19:06:52 -0000 1.16 ++++ mozilla/security/nss/lib/ssl/sslerr.h 16 Nov 2011 08:21:02 -0000 +@@ -203,14 +203,16 @@ SSL_ERROR_UNSAFE_NEGOTIATION + + SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD = (SSL_ERROR_BASE + 114), + + SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY = (SSL_ERROR_BASE + 115), + + SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID = (SSL_ERROR_BASE + 116), + + SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2 = (SSL_ERROR_BASE + 117), ++SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS = (SSL_ERROR_BASE + 118), ++SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_CLIENTS = (SSL_ERROR_BASE + 119), + + SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */ + } SSLErrorCodes; + #endif /* NO_SECURITY_ERROR_ENUM */ + + #endif /* __SSL_ERR_H_ */ +Index: mozilla/security/nss/lib/ssl/sslimpl.h +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/lib/ssl/sslimpl.h,v +retrieving revision 1.87 +diff -u -8 -p -r1.87 sslimpl.h +--- mozilla/security/nss/lib/ssl/sslimpl.h 11 Nov 2011 19:06:52 -0000 1.87 ++++ mozilla/security/nss/lib/ssl/sslimpl.h 16 Nov 2011 08:21:02 -0000 +@@ -745,16 +745,18 @@ struct TLSExtensionDataStr { + /* SNI Extension related data + * Names data is not coppied from the input buffer. It can not be + * used outside the scope where input buffer is defined and that + * is beyond ssl3_HandleClientHello function. */ + SECItem *sniNameArr; + PRUint32 sniNameArrSize; + }; + ++typedef SECStatus (*sslRestartTarget)(sslSocket *); ++ + /* + ** This is the "hs" member of the "ssl3" struct. + ** This entire struct is protected by ssl3HandshakeLock + */ + typedef struct SSL3HandshakeStateStr { + SSL3Random server_random; + SSL3Random client_random; + SSL3WaitState ws; +@@ -784,16 +786,23 @@ const ssl3CipherSuiteDef *suite_def; + union { + TLSFinished tFinished[2]; /* client, then server */ + SSL3Hashes sFinished[2]; + SSL3Opaque data[72]; + } finishedMsgs; + #ifdef NSS_ENABLE_ECC + PRUint32 negotiatedECCurves; /* bit mask */ + #endif /* NSS_ENABLE_ECC */ ++ ++ PRBool authCertificatePending; ++ /* Which function should SSL_RestartHandshake* call if we're blocked? ++ * One of NULL, ssl3_SendClientSecondRound, or ssl3_FinishHandshake. */ ++ sslRestartTarget restartTarget; ++ /* Shared state between ssl3_HandleFinished and ssl3_FinishHandshake */ ++ PRBool cacheSID; + } SSL3HandshakeState; + + + + /* + ** This is the "ssl3" struct, as in "ss->ssl3". + ** note: + ** usually, crSpec == cwSpec and prSpec == pwSpec. +@@ -1335,32 +1344,26 @@ extern SECStatus ssl3_KeyAndMacDeriveByp + PRBool isTLS, PRBool isExport); + extern SECStatus ssl3_MasterKeyDeriveBypass( ssl3CipherSpec * pwSpec, + const unsigned char * cr, const unsigned char * sr, + const SECItem * pms, PRBool isTLS, PRBool isRSA); + + /* These functions are called from secnav, even though they're "private". */ + + extern int ssl2_SendErrorMessage(struct sslSocketStr *ss, int error); +-extern int SSL_RestartHandshakeAfterServerCert(struct sslSocketStr *ss); + extern int SSL_RestartHandshakeAfterCertReq(struct sslSocketStr *ss, + CERTCertificate *cert, + SECKEYPrivateKey *key, + CERTCertificateList *certChain); + extern sslSocket *ssl_FindSocket(PRFileDesc *fd); + extern void ssl_FreeSocket(struct sslSocketStr *ssl); + extern SECStatus SSL3_SendAlert(sslSocket *ss, SSL3AlertLevel level, + SSL3AlertDescription desc); + +-extern SECStatus ssl3_RestartHandshakeAfterCertReq(sslSocket * ss, +- CERTCertificate * cert, +- SECKEYPrivateKey * key, +- CERTCertificateList *certChain); +- +-extern int ssl3_RestartHandshakeAfterServerCert(sslSocket *ss); ++extern SECStatus ssl3_RestartHandshakeAfterAuthCertificate(sslSocket *ss); + + /* + * for dealing with SSL 3.0 clients sending SSL 2.0 format hellos + */ + extern SECStatus ssl3_HandleV2ClientHello( + sslSocket *ss, unsigned char *buffer, int length); + extern SECStatus ssl3_StartHandshakeHash( + sslSocket *ss, unsigned char *buf, int length); +Index: mozilla/security/nss/lib/ssl/sslsecur.c +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/lib/ssl/sslsecur.c,v +retrieving revision 1.51 +diff -u -8 -p -r1.51 sslsecur.c +--- mozilla/security/nss/lib/ssl/sslsecur.c 11 Nov 2011 19:06:52 -0000 1.51 ++++ mozilla/security/nss/lib/ssl/sslsecur.c 16 Nov 2011 08:21:02 -0000 +@@ -1458,86 +1458,66 @@ SSL_CertDBHandleSet(PRFileDesc *fd, CERT + if (!dbHandle) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + ss->dbHandle = dbHandle; + return SECSuccess; + } + +-/* +- * attempt to restart the handshake after asynchronously handling +- * a request for the client's certificate. +- * +- * inputs: +- * cert Client cert chosen by application. +- * Note: ssl takes this reference, and does not bump the +- * reference count. The caller should drop its reference +- * without calling CERT_DestroyCert after calling this function. +- * +- * key Private key associated with cert. This function makes a +- * copy of the private key, so the caller remains responsible +- * for destroying its copy after this function returns. +- * +- * certChain Chain of signers for cert. +- * Note: ssl takes this reference, and does not copy the chain. +- * The caller should drop its reference without destroying the +- * chain. SSL will free the chain when it is done with it. +- * +- * Return value: XXX +- * +- * XXX This code only works on the initial handshake on a connection, XXX +- * It does not work on a subsequent handshake (redo). ++/* DO NOT USE. This function was exported in ssl.def with the wrong signature; ++ * this implementation exists to maintain link-time compatibility. + */ + int + SSL_RestartHandshakeAfterCertReq(sslSocket * ss, + CERTCertificate * cert, + SECKEYPrivateKey * key, + CERTCertificateList *certChain) + { +- int ret; +- +- ssl_Get1stHandshakeLock(ss); /************************************/ +- +- if (ss->version >= SSL_LIBRARY_VERSION_3_0) { +- ret = ssl3_RestartHandshakeAfterCertReq(ss, cert, key, certChain); +- } else { +- PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2); +- ret = SECFailure; +- } +- +- ssl_Release1stHandshakeLock(ss); /************************************/ +- return ret; ++ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); ++ return -1; + } + +- +-/* restart an SSL connection that we stopped to run certificate dialogs +-** XXX Need to document here how an application marks a cert to show that +-** the application has accepted it (overridden CERT_VerifyCert). +- * +- * XXX This code only works on the initial handshake on a connection, XXX +- * It does not work on a subsequent handshake (redo). +- * +- * Return value: XXX +-*/ ++/* DO NOT USE. This function was exported in ssl.def with the wrong signature; ++ * this implementation exists to maintain link-time compatibility. ++ */ + int +-SSL_RestartHandshakeAfterServerCert(sslSocket *ss) ++SSL_RestartHandshakeAfterServerCert(sslSocket * ss) ++{ ++ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); ++ return -1; ++} ++ ++/* See documentation in ssl.h */ ++SECStatus ++SSL_RestartHandshakeAfterAuthCertificate(PRFileDesc *fd) + { +- int rv = SECSuccess; ++ SECStatus rv = SECSuccess; ++ sslSocket *ss = ssl_FindSocket(fd); ++ ++ if (!ss) { ++ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_RestartHandshakeAfterPeerCert", ++ SSL_GETPID(), fd)); ++ return SECFailure; ++ } + +- ssl_Get1stHandshakeLock(ss); ++ ssl_Get1stHandshakeLock(ss); + +- if (ss->version >= SSL_LIBRARY_VERSION_3_0) { +- rv = ssl3_RestartHandshakeAfterServerCert(ss); ++ if (!ss->ssl3.initialized) { ++ PORT_SetError(SEC_ERROR_INVALID_ARGS); ++ rv = SECFailure; ++ } else if (ss->version < SSL_LIBRARY_VERSION_3_0) { ++ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2); ++ rv = SECFailure; + } else { +- PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2); +- rv = SECFailure; ++ rv = ssl3_RestartHandshakeAfterAuthCertificate(ss); + } + + ssl_Release1stHandshakeLock(ss); ++ + return rv; + } + + /* For more info see ssl.h */ + SECStatus + SSL_SNISocketConfigHook(PRFileDesc *fd, SSLSNISocketConfig func, + void *arg) + {
new file mode 100644 --- /dev/null +++ b/security/patches/bug-542832-ssl-restart-tstclnt-4.patch @@ -0,0 +1,349 @@ +Index: mozilla/security/nss/cmd/tstclnt/tstclnt.c +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/cmd/tstclnt/tstclnt.c,v +retrieving revision 1.64 +diff -u -8 -p -r1.64 tstclnt.c +--- mozilla/security/nss/cmd/tstclnt/tstclnt.c 6 Oct 2011 22:42:33 -0000 1.64 ++++ mozilla/security/nss/cmd/tstclnt/tstclnt.c 16 Nov 2011 08:24:12 -0000 +@@ -212,16 +212,18 @@ static void Usage(const char *progName) + "-n nickname"); + fprintf(stderr, + "%-20s Bypass PKCS11 layer for SSL encryption and MACing.\n", "-B"); + fprintf(stderr, "%-20s Disable SSL v2.\n", "-2"); + fprintf(stderr, "%-20s Disable SSL v3.\n", "-3"); + fprintf(stderr, "%-20s Disable TLS (SSL v3.1).\n", "-T"); + fprintf(stderr, "%-20s Prints only payload data. Skips HTTP header.\n", "-S"); + fprintf(stderr, "%-20s Client speaks first. \n", "-f"); ++ fprintf(stderr, "%-20s Use synchronous certificate validation " ++ "(required for SSL2)\n", "-O"); + fprintf(stderr, "%-20s Override bad server cert. Make it OK.\n", "-o"); + fprintf(stderr, "%-20s Disable SSL socket locking.\n", "-s"); + fprintf(stderr, "%-20s Verbose progress reporting.\n", "-v"); + fprintf(stderr, "%-20s Use export policy.\n", "-x"); + fprintf(stderr, "%-20s Ping the server and then exit.\n", "-q"); + fprintf(stderr, "%-20s Renegotiate N times (resuming session if N>1).\n", "-r N"); + fprintf(stderr, "%-20s Enable the session ticket extension.\n", "-u"); + fprintf(stderr, "%-20s Enable compression.\n", "-z"); +@@ -288,30 +290,54 @@ disableAllSSLCiphers(void) + fprintf(stderr, + "SSL_CipherPrefSet didn't like value 0x%04x (i = %d): %s\n", + suite, i, SECU_Strerror(err)); + exit(2); + } + } + } + ++typedef struct ++{ ++ PRBool shouldPause; /* PR_TRUE if we should use asynchronous peer cert ++ * authentication */ ++ PRBool isPaused; /* PR_TRUE if libssl is waiting for us to validate the ++ * peer's certificate and restart the handshake. */ ++ void * dbHandle; /* Certificate database handle to use while ++ * authenticating the peer's certificate. */ ++} ServerCertAuth; ++ + /* + * Callback is called when incoming certificate is not valid. + * Returns SECSuccess to accept the cert anyway, SECFailure to reject. + */ + static SECStatus + ownBadCertHandler(void * arg, PRFileDesc * socket) + { + PRErrorCode err = PR_GetError(); + /* can log invalid cert here */ + fprintf(stderr, "Bad server certificate: %d, %s\n", err, + SECU_Strerror(err)); + return SECSuccess; /* override, say it's OK. */ + } + ++static SECStatus ++ownAuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig, ++ PRBool isServer) ++{ ++ ServerCertAuth * serverCertAuth = (ServerCertAuth *) arg; ++ ++ FPRINTF(stderr, "using asynchronous certificate validation\n", progName); ++ ++ PORT_Assert(serverCertAuth->shouldPause); ++ PORT_Assert(!serverCertAuth->isPaused); ++ serverCertAuth->isPaused = PR_TRUE; ++ return SECWouldBlock; ++} ++ + SECStatus + own_GetClientAuthData(void * arg, + PRFileDesc * socket, + struct CERTDistNamesStr * caNames, + struct CERTCertificateStr ** pRetCert, + struct SECKEYPrivateKeyStr **pRetKey) + { + if (verbose > 1) { +@@ -493,21 +519,47 @@ separateReqHeader(const PRFileDesc* outF + } else if (((c) >= 'a') && ((c) <= 'f')) { \ + i = (c) - 'a' + 10; \ + } else if (((c) >= 'A') && ((c) <= 'F')) { \ + i = (c) - 'A' + 10; \ + } else { \ + Usage(progName); \ + } + ++static SECStatus ++restartHandshakeAfterServerCertIfNeeded(PRFileDesc * fd, ++ ServerCertAuth * serverCertAuth, ++ PRBool override) ++{ ++ SECStatus rv; ++ ++ if (!serverCertAuth->isPaused) ++ return SECSuccess; ++ ++ FPRINTF(stderr, "%s: handshake was paused by auth certificate hook\n", ++ progName); ++ ++ serverCertAuth->isPaused = PR_FALSE; ++ rv = SSL_AuthCertificate(serverCertAuth->dbHandle, fd, PR_TRUE, PR_FALSE); ++ if (rv != SECSuccess && override) { ++ rv = ownBadCertHandler(NULL, fd); ++ } ++ if (rv != SECSuccess) { ++ return rv; ++ } ++ ++ rv = SSL_RestartHandshakeAfterAuthCertificate(fd); ++ ++ return rv; ++} ++ + int main(int argc, char **argv) + { + PRFileDesc * s; + PRFileDesc * std_out; +- CERTCertDBHandle * handle; + char * host = NULL; + char * certDir = NULL; + char * nickname = NULL; + char * cipherString = NULL; + char * tmp; + int multiplier = 0; + SECStatus rv; + PRStatus status; +@@ -525,51 +577,58 @@ int main(int argc, char **argv) + int enableFalseStart = 0; + PRSocketOptionData opt; + PRNetAddr addr; + PRPollDesc pollset[2]; + PRBool pingServerFirst = PR_FALSE; + PRBool clientSpeaksFirst = PR_FALSE; + PRBool wrStarted = PR_FALSE; + PRBool skipProtoHeader = PR_FALSE; ++ ServerCertAuth serverCertAuth; + int headerSeparatorPtrnId = 0; + int error = 0; + PRUint16 portno = 443; + char * hs1SniHostName = NULL; + char * hs2SniHostName = NULL; + PLOptState *optstate; + PLOptStatus optstatus; + PRStatus prStatus; + ++ serverCertAuth.shouldPause = PR_TRUE; ++ serverCertAuth.isPaused = PR_FALSE; ++ serverCertAuth.dbHandle = NULL; ++ + progName = strrchr(argv[0], '/'); + if (!progName) + progName = strrchr(argv[0], '\\'); + progName = progName ? progName+1 : argv[0]; + + tmp = PR_GetEnv("NSS_DEBUG_TIMEOUT"); + if (tmp && tmp[0]) { + int sec = PORT_Atoi(tmp); + if (sec > 0) { + maxInterval = PR_SecondsToInterval(sec); + } + } + + optstate = PL_CreateOptState(argc, argv, +- "23BSTW:a:c:d:fgh:m:n:op:qr:suvw:xz"); ++ "23BOSTW:a:c:d:fgh:m:n:op:qr:suvw:xz"); + while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) { + switch (optstate->option) { + case '?': + default : Usage(progName); break; + + case '2': disableSSL2 = 1; break; + + case '3': disableSSL3 = 1; break; + + case 'B': bypassPKCS11 = 1; break; + ++ case 'O': serverCertAuth.shouldPause = PR_FALSE; break; ++ + case 'S': skipProtoHeader = PR_TRUE; break; + + case 'T': disableTLS = 1; break; + + case 'a': if (!hs1SniHostName) { + hs1SniHostName = PORT_Strdup(optstate->value); + } else if (!hs2SniHostName) { + hs2SniHostName = PORT_Strdup(optstate->value); +@@ -645,24 +704,18 @@ int main(int argc, char **argv) + } else { + char *certDirTmp = certDir; + certDir = SECU_ConfigDirectory(certDirTmp); + PORT_Free(certDirTmp); + } + rv = NSS_Init(certDir); + if (rv != SECSuccess) { + SECU_PrintError(progName, "unable to open cert database"); +-#if 0 +- rv = CERT_OpenVolatileCertDB(handle); +- CERT_SetDefaultCertDB(handle); +-#else + return 1; +-#endif + } +- handle = CERT_GetDefaultCertDB(); + + /* set the policy bits true for all the cipher suites. */ + if (useExportPolicy) + NSS_SetExportPolicy(); + else + NSS_SetDomesticPolicy(); + + /* all the SSL2 and SSL3 cipher suites are enabled by default. */ +@@ -871,17 +924,23 @@ int main(int argc, char **argv) + rv = SSL_OptionSet(s, SSL_ENABLE_FALSE_START, enableFalseStart); + if (rv != SECSuccess) { + SECU_PrintError(progName, "error enabling false start"); + return 1; + } + + SSL_SetPKCS11PinArg(s, &pwdata); + +- SSL_AuthCertificateHook(s, SSL_AuthCertificate, (void *)handle); ++ serverCertAuth.dbHandle = CERT_GetDefaultCertDB(); ++ ++ if (serverCertAuth.shouldPause) { ++ SSL_AuthCertificateHook(s, ownAuthCertificate, &serverCertAuth); ++ } else { ++ SSL_AuthCertificateHook(s, SSL_AuthCertificate, serverCertAuth.dbHandle); ++ } + if (override) { + SSL_BadCertHook(s, ownBadCertHandler, NULL); + } + SSL_GetClientAuthDataHook(s, own_GetClientAuthData, (void *)nickname); + SSL_HandshakeCallback(s, handshakeCallback, hs2SniHostName); + if (hs1SniHostName) { + SSL_SetURL(s, hs1SniHostName); + } else { +@@ -979,16 +1038,24 @@ int main(int argc, char **argv) + ** socket, read data from socket and write to stdout. + */ + FPRINTF(stderr, "%s: ready...\n", progName); + + while (pollset[SSOCK_FD].in_flags | pollset[STDIN_FD].in_flags) { + char buf[4000]; /* buffer for stdin */ + int nb; /* num bytes read from stdin. */ + ++ rv = restartHandshakeAfterServerCertIfNeeded(s, &serverCertAuth, ++ override); ++ if (rv != SECSuccess) { ++ error = 254; /* 254 (usually) means "handshake failed" */ ++ SECU_PrintError(progName, "authentication of server cert failed"); ++ goto done; ++ } ++ + pollset[SSOCK_FD].out_flags = 0; + pollset[STDIN_FD].out_flags = 0; + + FPRINTF(stderr, "%s: about to call PR_Poll !\n", progName); + filesReady = PR_Poll(pollset, npds, PR_INTERVAL_NO_TIMEOUT); + if (filesReady < 0) { + SECU_PrintError(progName, "select failed"); + error = 1; +@@ -1037,16 +1104,25 @@ int main(int argc, char **argv) + goto done; + } + cc = 0; + } + bufp += cc; + nb -= cc; + if (nb <= 0) + break; ++ ++ rv = restartHandshakeAfterServerCertIfNeeded(s, ++ &serverCertAuth, override); ++ if (rv != SECSuccess) { ++ error = 254; /* 254 (usually) means "handshake failed" */ ++ SECU_PrintError(progName, "authentication of server cert failed"); ++ goto done; ++ } ++ + pollset[SSOCK_FD].in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT; + pollset[SSOCK_FD].out_flags = 0; + FPRINTF(stderr, + "%s: about to call PR_Poll on writable socket !\n", + progName); + cc = PR_Poll(pollset, 1, PR_INTERVAL_NO_TIMEOUT); + FPRINTF(stderr, + "%s: PR_Poll returned with writable socket !\n", +Index: mozilla/security/nss/tests/ssl/ssl.sh +=================================================================== +RCS file: /cvsroot/mozilla/security/nss/tests/ssl/ssl.sh,v +retrieving revision 1.106 +diff -u -8 -p -r1.106 ssl.sh +--- mozilla/security/nss/tests/ssl/ssl.sh 29 Jan 2010 22:36:25 -0000 1.106 ++++ mozilla/security/nss/tests/ssl/ssl.sh 16 Nov 2011 08:24:14 -0000 +@@ -303,16 +303,26 @@ ssl_cov() + + exec < ${SSLCOV} + while read ectype tls param testname + do + echo "${testname}" | grep "EXPORT" > /dev/null + EXP=$? + echo "${testname}" | grep "SSL2" > /dev/null + SSL2=$? ++ ++ if [ "${SSL2}" -eq 0 ] ; then ++ # We cannot use asynchronous cert verification with SSL2 ++ SSL2_FLAGS=-O ++ else ++ # Do not enable SSL2 for non-SSL2-specific tests. SSL2 is disabled by ++ # default in libssl but it is enabled by default in tstclnt; we want ++ # to test the libssl default whenever possible. ++ SSL2_FLAGS=-2 ++ fi + + if [ "$NORM_EXT" = "Extended Test" -a "${SSL2}" -eq 0 ] ; then + echo "$SCRIPTNAME: skipping $testname for $NORM_EXT" + elif [ "$ectype" = "ECC" -a -z "$NSS_ENABLE_ECC" ] ; then + echo "$SCRIPTNAME: skipping $testname (ECC only)" + elif [ "$SERVER_MODE" = "fips" -o "$CLIENT_MODE" = "fips" ] && [ "$SSL2" -eq 0 -o "$EXP" -eq 0 ] ; then + echo "$SCRIPTNAME: skipping $testname (non-FIPS only)" + elif [ "`echo $ectype | cut -b 1`" != "#" ] ; then +@@ -345,21 +355,21 @@ ssl_cov() + is_selfserv_alive + else + kill_selfserv + start_selfserv + mixed=0 + fi + fi + +- echo "tstclnt -p ${PORT} -h ${HOSTADDR} -c ${param} ${TLS_FLAG} ${CLIENT_OPTIONS} \\" ++ echo "tstclnt -p ${PORT} -h ${HOSTADDR} -c ${param} ${TLS_FLAG} ${SSL2_FLAGS} ${CLIENT_OPTIONS} \\" + echo " -f -d ${P_R_CLIENTDIR} -v -w nss < ${REQUEST_FILE}" + + rm ${TMP}/$HOST.tmp.$$ 2>/dev/null +- ${PROFTOOL} ${BINDIR}/tstclnt -p ${PORT} -h ${HOSTADDR} -c ${param} ${TLS_FLAG} ${CLIENT_OPTIONS} -f \ ++ ${PROFTOOL} ${BINDIR}/tstclnt -p ${PORT} -h ${HOSTADDR} -c ${param} ${TLS_FLAG} ${SSL2_FLAGS} ${CLIENT_OPTIONS} -f \ + -d ${P_R_CLIENTDIR} -v -w nss < ${REQUEST_FILE} \ + >${TMP}/$HOST.tmp.$$ 2>&1 + ret=$? + cat ${TMP}/$HOST.tmp.$$ + rm ${TMP}/$HOST.tmp.$$ 2>/dev/null + html_msg $ret 0 "${testname}" \ + "produced a returncode of $ret, expected is 0" + fi
--- a/storage/src/mozStorageStatementJSHelper.cpp +++ b/storage/src/mozStorageStatementJSHelper.cpp @@ -61,19 +61,23 @@ namespace storage { static JSBool stepFunc(JSContext *aCtx, PRUint32, jsval *_vp) { nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect()); nsCOMPtr<nsIXPConnectWrappedNative> wrapper; - nsresult rv = xpc->GetWrappedNativeOfJSObject( - aCtx, JS_THIS_OBJECT(aCtx, _vp), getter_AddRefs(wrapper) - ); + JSObject *obj = JS_THIS_OBJECT(aCtx, _vp); + if (!obj) { + return JS_FALSE; + } + + nsresult rv = + xpc->GetWrappedNativeOfJSObject(aCtx, obj, getter_AddRefs(wrapper)); if (NS_FAILED(rv)) { ::JS_ReportError(aCtx, "mozIStorageStatement::step() could not obtain native statement"); return JS_FALSE; } #ifdef DEBUG { nsCOMPtr<mozIStorageStatement> isStatement(
--- a/testing/mochitest/browser-test.js +++ b/testing/mochitest/browser-test.js @@ -205,16 +205,20 @@ Tester.prototype = { try { func.apply(testScope); } catch (ex) { this.currentTest.addResult(new testResult(false, "Cleanup function threw an exception", ex, false)); } }; + if (this.SimpleTest.isExpectingUncaughtException()) { + this.currentTest.addResult(new testResult(false, "expectUncaughtException was called but no uncaught exception was detected!", "", false)); + } + // Clear document.popupNode. The test could have set it to a custom value // for its own purposes, nulling it out it will go back to the default // behavior of returning the last opened popup. document.popupNode = null; // Note the test run time let time = Date.now() - this.lastStartTime; this.dumper.dump("INFO TEST-END | " + this.currentTest.path + " | finished in " + time + "ms\n"); @@ -245,26 +249,28 @@ Tester.prototype = { this.currentTestIndex++; this.execTest(); }).bind(this)); }, execTest: function Tester_execTest() { this.dumper.dump("TEST-START | " + this.currentTest.path + "\n"); + this.SimpleTest.reset(); + // Load the tests into a testscope this.currentTest.scope = new testScope(this, this.currentTest); // Import utils in the test scope. this.currentTest.scope.EventUtils = this.EventUtils; this.currentTest.scope.SimpleTest = this.SimpleTest; this.currentTest.scope.gTestPath = this.currentTest.path; // Override SimpleTest methods with ours. - ["ok", "is", "isnot", "todo", "todo_is", "todo_isnot"].forEach(function(m) { + ["ok", "is", "isnot", "todo", "todo_is", "todo_isnot", "info"].forEach(function(m) { this.SimpleTest[m] = this[m]; }, this.currentTest.scope); //load the tools to work with chrome .jar and remote try { this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", this.currentTest.scope); } catch (ex) { /* no chrome-harness tools */ } @@ -291,17 +297,23 @@ Tester.prototype = { this.currentTest.scope.waitForExplicitFinish(); var result = this.currentTest.scope.generatorTest(); this.currentTest.scope.__generator = result; result.next(); } else { this.currentTest.scope.test(); } } catch (ex) { - this.currentTest.addResult(new testResult(false, "Exception thrown", ex, false)); + var isExpected = !!this.SimpleTest.isExpectingUncaughtException(); + if (!this.SimpleTest.isIgnoringAllUncaughtExceptions()) { + this.currentTest.addResult(new testResult(isExpected, "Exception thrown", ex, false)); + this.SimpleTest.expectUncaughtException(false); + } else { + this.currentTest.addResult(new testMessage("Exception thrown: " + ex)); + } this.currentTest.scope.finish(); } // If the test ran synchronously, move to the next test, otherwise the test // will trigger the next test when it is done. if (this.currentTest.scope.__done) { this.nextTest(); } @@ -374,45 +386,44 @@ function testMessage(aName) { this.info = true; this.result = "TEST-INFO"; } // Need to be careful adding properties to this object, since its properties // cannot conflict with global variables used in tests. function testScope(aTester, aTest) { this.__tester = aTester; - this.__browserTest = aTest; var self = this; this.ok = function test_ok(condition, name, diag, stack) { - self.__browserTest.addResult(new testResult(condition, name, diag, false, - stack ? stack : Components.stack.caller)); + aTest.addResult(new testResult(condition, name, diag, false, + stack ? stack : Components.stack.caller)); }; this.is = function test_is(a, b, name) { self.ok(a == b, name, "Got " + a + ", expected " + b, false, Components.stack.caller); }; this.isnot = function test_isnot(a, b, name) { self.ok(a != b, name, "Didn't expect " + a + ", but got it", false, Components.stack.caller); }; this.todo = function test_todo(condition, name, diag, stack) { - self.__browserTest.addResult(new testResult(!condition, name, diag, true, - stack ? stack : Components.stack.caller)); + aTest.addResult(new testResult(!condition, name, diag, true, + stack ? stack : Components.stack.caller)); }; this.todo_is = function test_todo_is(a, b, name) { self.todo(a == b, name, "Got " + a + ", expected " + b, Components.stack.caller); }; this.todo_isnot = function test_todo_isnot(a, b, name) { self.todo(a != b, name, "Didn't expect " + a + ", but got it", Components.stack.caller); }; this.info = function test_info(name) { - self.__browserTest.addResult(new testMessage(name)); + aTest.addResult(new testMessage(name)); }; this.executeSoon = function test_executeSoon(func) { let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); tm.mainThread.dispatch({ run: function() { func(); @@ -433,17 +444,23 @@ function testScope(aTester, aTest) { } try { self.__generator.send(arg); } catch (ex if ex instanceof StopIteration) { // StopIteration means test is finished. self.finish(); } catch (ex) { - aTest.addResult(new testResult(false, "Exception thrown", ex, false)); + var isExpected = !!self.SimpleTest.isExpectingUncaughtException(); + if (!self.SimpleTest.isIgnoringAllUncaughtExceptions()) { + aTest.addResult(new testResult(isExpected, "Exception thrown", ex, false)); + self.SimpleTest.expectUncaughtException(false); + } else { + aTest.addResult(new testMessage("Exception thrown: " + ex)); + } self.finish(); } }; this.waitForExplicitFinish = function test_waitForExplicitFinish() { self.__done = false; }; @@ -462,29 +479,26 @@ function testScope(aTester, aTest) { this.requestLongerTimeout = function test_requestLongerTimeout(aFactor) { self.__timeoutFactor = aFactor; }; this.copyToProfile = function test_copyToProfile(filename) { self.SimpleTest.copyToProfile(filename); }; - this.expectUncaughtException = function test_expectUncaughtException() { - self.SimpleTest.expectUncaughtException(); + this.expectUncaughtException = function test_expectUncaughtException(aExpecting) { + self.SimpleTest.expectUncaughtException(aExpecting); }; - this.ignoreAllUncaughtExceptions = function test_ignoreAllUncaughtExceptions() { - self.SimpleTest.ignoreAllUncaughtExceptions(); + this.ignoreAllUncaughtExceptions = function test_ignoreAllUncaughtExceptions(aIgnoring) { + self.SimpleTest.ignoreAllUncaughtExceptions(aIgnoring); }; this.finish = function test_finish() { self.__done = true; - if (self.SimpleTest._expectingUncaughtException) { - self.ok(false, "expectUncaughtException was called but no uncaught exception was detected!"); - } if (self.__waitTimer) { self.executeSoon(function() { if (self.__done && self.__waitTimer) { clearTimeout(self.__waitTimer); self.__waitTimer = null; self.__tester.nextTest(); } });
--- a/testing/mochitest/chrome/Makefile.in +++ b/testing/mochitest/chrome/Makefile.in @@ -42,15 +42,14 @@ VPATH = @srcdir@ relativesrcdir = testing/mochitest/chrome include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/rules.mk _STATIC_FILES = test_sample.xul \ test_sanityChromeUtils.xul \ test_sanityPluginUtils.html \ -# Disabled until bug 652494 is resolved. -# test_sanityException.xul \ -# test_sanityException2.xul \ + test_sanityException.xul \ + test_sanityException2.xul \ $(NULL) libs:: $(_STATIC_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/$(relativesrcdir)
--- a/testing/mochitest/chrome/test_sanityException.xul +++ b/testing/mochitest/chrome/test_sanityException.xul @@ -10,14 +10,14 @@ https://bugzilla.mozilla.org/show_bug.cg <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> <body xmlns="http://www.w3.org/1999/xhtml"> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=670817">Mozilla Bug 670817</a> <script type="application/javascript"><![CDATA[ SimpleTest.expectUncaughtException(); ok(true, "a call to ok"); -throw "uncaught exception"; +throw "this is a deliberately thrown exception"; ]]></script> </body> </window>
--- a/testing/mochitest/chrome/test_sanityException2.xul +++ b/testing/mochitest/chrome/test_sanityException2.xul @@ -12,17 +12,17 @@ https://bugzilla.mozilla.org/show_bug.cg <body xmlns="http://www.w3.org/1999/xhtml"> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=670817">Mozilla Bug 670817</a> <script type="application/javascript"><![CDATA[ SimpleTest.waitForExplicitFinish(); ok(true, "a call to ok"); SimpleTest.executeSoon(function() { SimpleTest.expectUncaughtException(); - throw "an uncaught exception"; + throw "this is a deliberately thrown exception"; }); SimpleTest.executeSoon(function() { SimpleTest.finish(); }); ]]></script> </body>
--- a/testing/mochitest/tests/SimpleTest/SimpleTest.js +++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js @@ -688,27 +688,53 @@ SimpleTest.expectChildProcessCrash = fun parentRunner.expectChildProcessCrash(); } }; /** * Indicates to the test framework that the next uncaught exception during * the test is expected, and should not cause a test failure. */ -SimpleTest.expectUncaughtException = function () { - SimpleTest._expectingUncaughtException = true; +SimpleTest.expectUncaughtException = function (aExpecting) { + SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting; +}; + +/** + * Returns whether the test has indicated that it expects an uncaught exception + * to occur. + */ +SimpleTest.isExpectingUncaughtException = function () { + return SimpleTest._expectingUncaughtException; }; /** * Indicates to the test framework that all of the uncaught exceptions * during the test are known problems that should be fixed in the future, * but which should not cause the test to fail currently. */ -SimpleTest.ignoreAllUncaughtExceptions = function () { - SimpleTest._ignoringAllUncaughtExceptions = true; +SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) { + SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring; +}; + +/** + * Returns whether the test has indicated that all uncaught exceptions should be + * ignored. + */ +SimpleTest.isIgnoringAllUncaughtExceptions = function () { + return SimpleTest._ignoringAllUncaughtExceptions; +}; + +/** + * Resets any state this SimpleTest object has. This is important for + * browser chrome mochitests, which reuse the same SimpleTest object + * across a run. + */ +SimpleTest.reset = function () { + SimpleTest._ignoringAllUncaughtExceptions = false; + SimpleTest._expectingUncaughtException = false; }; if (isPrimaryTestWindow) { addLoadEvent(function() { if (SimpleTest._stopOnLoad) { SimpleTest.finish(); } }); @@ -919,32 +945,31 @@ var isnot = SimpleTest.isnot; var todo = SimpleTest.todo; var todo_is = SimpleTest.todo_is; var todo_isnot = SimpleTest.todo_isnot; var isDeeply = SimpleTest.isDeeply; var info = SimpleTest.info; var gOldOnError = window.onerror; window.onerror = function simpletestOnerror(errorMsg, url, lineNumber) { - var funcIdentifier = "[SimpleTest/SimpleTest.js, window.onerror]"; - // Log the message. // XXX Chrome mochitests sometimes trigger this window.onerror handler, // but there are a number of uncaught JS exceptions from those tests. // For now, for tests that self identify as having unintentional uncaught // exceptions, just dump it so that the error is visible but doesn't cause // a test failure. See bug 652494. - var message = "An error occurred: " + errorMsg + " at " + url + ":" + lineNumber; var href = SpecialPowers.getPrivilegedProps(window, 'location.href'); var isExpected = !!SimpleTest._expectingUncaughtException; + var message = "an " + (isExpected ? "" : "un") + "expected uncaught JS exception reported through window.onerror"; + var error = errorMsg + " at " + url + ":" + lineNumber; if (!SimpleTest._ignoringAllUncaughtExceptions) { - SimpleTest.ok(isExpected, funcIdentifier, message); + SimpleTest.ok(isExpected, message, error); SimpleTest._expectingUncaughtException = false; } else { - SimpleTest.todo(false, funcIdentifier, message); + SimpleTest.todo(false, message + ": " + error); } // There is no Components.stack.caller to log. (See bug 511888.) // Call previous handler. if (gOldOnError) { try { // Ignore return value: always run default handler. gOldOnError(errorMsg, url, lineNumber);
--- a/testing/mochitest/tests/browser/Makefile.in +++ b/testing/mochitest/tests/browser/Makefile.in @@ -47,19 +47,18 @@ include $(topsrcdir)/config/rules.mk _BROWSER_TEST_FILES = \ head.js \ browser_head.js \ browser_pass.js \ browser_async.js \ browser_privileges.js \ browser_popupNode.js \ browser_popupNode_check.js \ -# Disabled until bug 652494 is resolved. -# browser_sanityException.js \ -# browser_sanityException2.js \ + browser_sanityException.js \ + browser_sanityException2.js \ # Disabled, these are only good for testing the harness' failure reporting # browser_zz_fail_openwindow.js \ # browser_fail.js \ # browser_fail_async_throw.js \ # browser_fail_fp.js \ # browser_fail_pf.js \ # browser_fail_throw.js \ # browser_fail_timeout.js \
--- a/testing/mochitest/tests/browser/browser_sanityException.js +++ b/testing/mochitest/tests/browser/browser_sanityException.js @@ -1,5 +1,5 @@ function test() { ok(true, "ok called"); expectUncaughtException(); - throw "uncaught exception"; + throw "this is a deliberately thrown exception"; }
--- a/testing/mochitest/tests/browser/browser_sanityException2.js +++ b/testing/mochitest/tests/browser/browser_sanityException2.js @@ -1,11 +1,11 @@ function test() { waitForExplicitFinish(); ok(true, "ok called"); executeSoon(function() { expectUncaughtException(); - throw "uncaught exception"; + throw "this is a deliberately thrown exception"; }); executeSoon(function() { finish(); }); }
--- a/toolkit/components/places/AsyncFaviconHelpers.cpp +++ b/toolkit/components/places/AsyncFaviconHelpers.cpp @@ -16,16 +16,17 @@ * The Original Code is Places. * * The Initial Developer of the Original Code is the Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Marco Bonardo <mak77@bonardo.net> (original author) + * Richard Newman <rnewman@mozilla.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -719,33 +720,50 @@ AsyncAssociateIconToPage::Run() NS_ENSURE_SUCCESS(rv, rv); } mozStorageTransaction transaction(mDB->MainConn(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); // If there is no entry for this icon, or the entry is obsolete, replace it. if (mIcon.id == 0 || (mIcon.status & ICON_STATUS_CHANGED)) { + // The 'multi-coalesce' here ensures that replacing a favicon without + // specifying a :guid parameter doesn't cause it to be allocated a new + // GUID. nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( "INSERT OR REPLACE INTO moz_favicons " - "(id, url, data, mime_type, expiration) " + "(id, url, data, mime_type, expiration, guid) " "VALUES ((SELECT id FROM moz_favicons WHERE url = :icon_url), " - ":icon_url, :data, :mime_type, :expiration) " + ":icon_url, :data, :mime_type, :expiration, " + "COALESCE(:guid, " + "(SELECT guid FROM moz_favicons " + "WHERE url = :icon_url), " + "GENERATE_GUID()))" ); NS_ENSURE_STATE(stmt); mozStorageStatementScoper scoper(stmt); rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), mIcon.spec); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->BindBlobByName(NS_LITERAL_CSTRING("data"), TO_INTBUFFER(mIcon.data), mIcon.data.Length()); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), mIcon.mimeType); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), mIcon.expiration); NS_ENSURE_SUCCESS(rv, rv); + + // Binding a GUID allows us to override the current (or generated) GUID. + if (mIcon.guid.IsEmpty()) { + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("guid")); + } + else { + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), mIcon.guid); + } + NS_ENSURE_SUCCESS(rv, rv); + rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, rv); // Get the new icon id. Do this regardless mIcon.id, since other code // could have added a entry before us. Indeed we interrupted the thread // after the previous call to FetchIconInfo. rv = FetchIconInfo(mDB, mIcon); NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/AsyncFaviconHelpers.h +++ b/toolkit/components/places/AsyncFaviconHelpers.h @@ -70,25 +70,27 @@ enum AsyncFaviconFetchMode { struct IconData { IconData() : id(0) , expiration(0) , fetchMode(FETCH_NEVER) , status(ICON_STATUS_UNKNOWN) { + guid.SetIsVoid(PR_TRUE); } PRInt64 id; nsCString spec; nsCString data; nsCString mimeType; PRTime expiration; enum AsyncFaviconFetchMode fetchMode; PRUint16 status; // This is a bitset, see ICON_STATUS_* defines above. + nsCString guid; }; /** * Data cache for a page entry. */ struct PageData { PageData()
--- a/toolkit/components/places/Database.cpp +++ b/toolkit/components/places/Database.cpp @@ -28,16 +28,17 @@ * Asaf Romano <mano@mozilla.com> * Marco Bonardo <mak77@bonardo.net> * Edward Lee <edward.lee@engineering.uiuc.edu> * Michael Ventnor <m.ventnor@gmail.com> * Ehsan Akhgari <ehsan.akhgari@gmail.com> * Drew Willcoxon <adw@mozilla.com> * Philipp von Weitershausen <philipp@weitershausen.de> * Paolo Amadini <http://www.amadzone.org/> + * Richard Newman <rnewman@mozilla.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -637,17 +638,23 @@ Database::InitSchema(bool* aDatabaseMigr // Firefox 4 uses schema version 11. // Firefox 8 uses schema version 12. if (currentSchemaVersion < 13) { rv = MigrateV13Up(); NS_ENSURE_SUCCESS(rv, rv); } - // Firefox 11 uses schema version 13. + + if (currentSchemaVersion < 14) { + rv = MigrateV14Up(); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Firefox 11 uses schema version 14. // Schema Upgrades must add migration code here. } } else { // This is a new database, so we have to create all the tables and indices. // moz_places. @@ -945,17 +952,17 @@ Database::MigrateV7Up() NS_ENSURE_SUCCESS(rv, rv); if (!URLUniqueIndexExists) { return NS_ERROR_FILE_CORRUPTED; } mozStorageTransaction transaction(mMainConn, false); // We need an index on lastModified to catch quickly last modified bookmark - // title for tag container's children. This will be useful for sync too. + // title for tag container's children. This will be useful for Sync, too. bool lastModIndexExists = false; rv = mMainConn->IndexExists( NS_LITERAL_CSTRING("moz_bookmarks_itemlastmodifiedindex"), &lastModIndexExists); NS_ENSURE_SUCCESS(rv, rv); if (!lastModIndexExists) { rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED); @@ -1277,18 +1284,18 @@ Database::MigrateV11Up() "ALTER TABLE moz_bookmarks " "ADD COLUMN guid TEXT" )); NS_ENSURE_SUCCESS(rv, rv); rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID); NS_ENSURE_SUCCESS(rv, rv); - // moz_placess grew a guid column. Add the column, but do not populate it - // with anything just yet. We will do that soon. + // moz_places grew a guid column. Add the column, but do not populate it + // with anything just yet. We will do that soon. rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "ALTER TABLE moz_places " "ADD COLUMN guid TEXT" )); NS_ENSURE_SUCCESS(rv, rv); rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID); NS_ENSURE_SUCCESS(rv, rv); @@ -1301,35 +1308,65 @@ Database::MigrateV11Up() return NS_OK; } nsresult Database::MigrateV13Up() { MOZ_ASSERT(NS_IsMainThread()); - // Dynamic containers are no more supported. - - // For existing profiles, we may not have a moz_bookmarks.guid column + // Dynamic containers are no longer supported. nsCOMPtr<mozIStorageAsyncStatement> deleteDynContainersStmt; nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING( "DELETE FROM moz_bookmarks WHERE type = :item_type"), getter_AddRefs(deleteDynContainersStmt)); rv = deleteDynContainersStmt->BindInt32ByName( NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_DYNAMIC_CONTAINER ); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<mozIStoragePendingStatement> ps; rv = deleteDynContainersStmt->ExecuteAsync(nsnull, getter_AddRefs(ps)); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } +nsresult +Database::MigrateV14Up() +{ + // For existing profiles, we may not have a moz_favicons.guid column. + // Add it here. We want it to be unique, but ALTER TABLE doesn't allow + // a uniqueness constraint, so the index must be created separately. + nsCOMPtr<mozIStorageStatement> hasGuidStatement; + nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( + "SELECT guid FROM moz_favicons"), + getter_AddRefs(hasGuidStatement)); + + if (NS_FAILED(rv)) { + rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE moz_favicons " + "ADD COLUMN guid TEXT" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // Generate GUIDs for our existing favicons. + rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "UPDATE moz_favicons " + "SET guid = GENERATE_GUID()" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // And now we can make the column unique. + rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_FAVICONS_GUID); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + void Database::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mShuttingDown); mMainThreadStatements.FinalizeStatements(); mMainThreadAsyncStatements.FinalizeStatements(); @@ -1407,16 +1444,20 @@ Database::Observe(nsISupports *aSubject, nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT 1 " "FROM moz_places " "WHERE guid IS NULL " "UNION ALL " "SELECT 1 " "FROM moz_bookmarks " "WHERE guid IS NULL " + "UNION ALL " + "SELECT 1 " + "FROM moz_favicons " + "WHERE guid IS NULL " ), getter_AddRefs(stmt)); NS_ENSURE_SUCCESS(rv, rv); bool haveNullGuids; rv = stmt->ExecuteStep(&haveNullGuids); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(!haveNullGuids, "Someone added an entry without adding a GUID!");
--- a/toolkit/components/places/Database.h +++ b/toolkit/components/places/Database.h @@ -40,19 +40,19 @@ #include "nsThreadUtils.h" #include "nsWeakReference.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIObserver.h" #include "mozilla/storage.h" #include "mozilla/storage/StatementCache.h" -// This is the schema version, update it at any schema change and add a +// This is the schema version. Update it at any schema change and add a // corresponding migrateVxx method below. -#define DATABASE_SCHEMA_VERSION 13 +#define DATABASE_SCHEMA_VERSION 14 // Fired after Places inited. #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete" // Fired when initialization fails due to a locked database. #define TOPIC_DATABASE_LOCKED "places-database-locked" // This topic is received when the profile is about to be lost. Places does // initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners. // Any shutdown work that requires the Places APIs should happen here. @@ -283,18 +283,20 @@ protected: /** * Helpers used by schema upgrades. */ nsresult MigrateV7Up(); nsresult MigrateV8Up(); nsresult MigrateV9Up(); nsresult MigrateV10Up(); nsresult MigrateV11Up(); + nsresult MigrateV13Up(); + nsresult MigrateV14Up(); + nsresult CheckAndUpdateGUIDs(); - nsresult MigrateV13Up(); private: ~Database(); /** * Singleton getter, invoked by class instantiation. * * Note: does AddRef.
--- a/toolkit/components/places/nsFaviconService.cpp +++ b/toolkit/components/places/nsFaviconService.cpp @@ -233,22 +233,25 @@ nsFaviconService::SetFaviconUrlForPage(n } } mozStorageTransaction transaction(mDB->MainConn(), false); if (iconId == -1) { // We did not find any entry for this icon, so create a new one. nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( - "INSERT INTO moz_favicons (id, url, data, mime_type, expiration) " - "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration)" + "INSERT INTO moz_favicons (id, url, data, mime_type, expiration, guid) " + "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration, " + "COALESCE(:guid, GENERATE_GUID()))" ); NS_ENSURE_STATE(stmt); mozStorageStatementScoper scoper(stmt); + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("guid")); + NS_ENSURE_SUCCESS(rv, rv); rv = stmt->BindNullByName(NS_LITERAL_CSTRING("icon_id")); NS_ENSURE_SUCCESS(rv, rv); rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), aFaviconURI); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->BindNullByName(NS_LITERAL_CSTRING("data")); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->BindNullByName(NS_LITERAL_CSTRING("mime_type")); NS_ENSURE_SUCCESS(rv, rv); @@ -438,49 +441,56 @@ nsFaviconService::SetFaviconData(nsIURI* NS_ENSURE_SUCCESS(rv, rv); if (hasResult) { // Get id of the old entry and update it. PRInt64 id; rv = stmt->GetInt64(0, &id); NS_ENSURE_SUCCESS(rv, rv); statement = mDB->GetStatement( - "UPDATE moz_favicons SET data = :data, mime_type = :mime_type, " - "expiration = :expiration " + "UPDATE moz_favicons SET " + "guid = COALESCE(:guid, guid), " + "data = :data, " + "mime_type = :mime_type, " + "expiration = :expiration " "WHERE id = :icon_id" ); NS_ENSURE_STATE(statement); + rv = statement->BindNullByName(NS_LITERAL_CSTRING("guid")); + NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), id); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindBlobByName(NS_LITERAL_CSTRING("data"), data, dataLen); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), *mimeType); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), aExpiration); NS_ENSURE_SUCCESS(rv, rv); } else { // Insert a new entry. statement = mDB->GetStatement( - "INSERT INTO moz_favicons (id, url, data, mime_type, expiration) " - "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration)" - ); + "INSERT INTO moz_favicons (id, url, data, mime_type, expiration, guid) " + "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration, " + "COALESCE(:guid, GENERATE_GUID()))"); NS_ENSURE_STATE(statement); rv = statement->BindNullByName(NS_LITERAL_CSTRING("icon_id")); NS_ENSURE_SUCCESS(rv, rv); rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("icon_url"), aFaviconURI); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindBlobByName(NS_LITERAL_CSTRING("data"), data, dataLen); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), *mimeType); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), aExpiration); NS_ENSURE_SUCCESS(rv, rv); + rv = statement->BindNullByName(NS_LITERAL_CSTRING("guid")); + NS_ENSURE_SUCCESS(rv, rv); } } mozStorageStatementScoper statementScoper(statement); rv = statement->Execute(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK;
--- a/toolkit/components/places/nsNavHistory.cpp +++ b/toolkit/components/places/nsNavHistory.cpp @@ -25,16 +25,17 @@ * Asaf Romano <mano@mozilla.com> * Marco Bonardo <mak77@bonardo.net> * Edward Lee <edward.lee@engineering.uiuc.edu> * Michael Ventnor <m.ventnor@gmail.com> * Ehsan Akhgari <ehsan.akhgari@gmail.com> * Drew Willcoxon <adw@mozilla.com> * Philipp von Weitershausen <philipp@weitershausen.de> * Paolo Amadini <http://www.amadzone.org/> + * Richard Newman <rnewman@mozilla.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your
--- a/toolkit/components/places/nsPlacesIndexes.h +++ b/toolkit/components/places/nsPlacesIndexes.h @@ -140,10 +140,18 @@ * moz_items_annos */ #define CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE \ CREATE_PLACES_IDX( \ "itemattributeindex", "moz_items_annos", "item_id, anno_attribute_id", "UNIQUE" \ ) +/** + * moz_favicons + */ + +#define CREATE_IDX_MOZ_FAVICONS_GUID \ + CREATE_PLACES_IDX( \ + "guid_uniqueindex", "moz_favicons", "guid", "UNIQUE" \ + ) #endif // nsPlacesIndexes_h__
--- a/toolkit/components/places/nsPlacesTables.h +++ b/toolkit/components/places/nsPlacesTables.h @@ -17,16 +17,17 @@ * * The Initial Developer of the Original Code is * Mozilla Corporation. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Shawn Wilsher <me@shawnwilsher.com> (Original Author) + * Richard Newman <rnewman@mozilla.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -122,16 +123,17 @@ #define CREATE_MOZ_FAVICONS NS_LITERAL_CSTRING( \ "CREATE TABLE moz_favicons (" \ " id INTEGER PRIMARY KEY" \ ", url LONGVARCHAR UNIQUE" \ ", data BLOB" \ ", mime_type VARCHAR(32)" \ ", expiration LONG" \ + ", guid TEXT" \ ")" \ ) #define CREATE_MOZ_BOOKMARKS NS_LITERAL_CSTRING( \ "CREATE TABLE moz_bookmarks (" \ " id INTEGER PRIMARY KEY" \ ", type INTEGER" \ ", fk INTEGER DEFAULT NULL" /* place_id */ \
--- a/toolkit/components/places/tests/cpp/mock_Link.h +++ b/toolkit/components/places/tests/cpp/mock_Link.h @@ -52,35 +52,44 @@ public: NS_DECL_ISUPPORTS mock_Link(void (*aHandlerFunction)(nsLinkState), bool aRunNextTest = true) : mozilla::dom::Link(nsnull) , mHandler(aHandlerFunction) , mRunNextTest(aRunNextTest) { + // Create a cyclic ownership, so that the link will be released only + // after its status has been updated. This will ensure that, when it should + // run the next test, it will happen at the end of the test function, if + // the link status has already been set before. Indeed the link status is + // updated on a separate connection, thus may happen at any time. + mDeathGrip = this; } virtual void SetLinkState(nsLinkState aState) { // Notify our callback function. mHandler(aState); + // Break the cycle so the object can be destroyed. + mDeathGrip = 0; + } + + ~mock_Link() { // Run the next test if we are supposed to. if (mRunNextTest) { run_next_test(); } - - // Finally, we must manually release ourselves. - NS_RELEASE_THIS(); } private: void (*mHandler)(nsLinkState); bool mRunNextTest; + nsRefPtr<Link> mDeathGrip; }; NS_IMPL_ISUPPORTS1( mock_Link, mozilla::dom::Link ) ////////////////////////////////////////////////////////////////////////////////
--- a/toolkit/components/places/tests/cpp/test_IHistory.cpp +++ b/toolkit/components/places/tests/cpp/test_IHistory.cpp @@ -188,17 +188,16 @@ test_visited_notifies() // have on the Link. nsRefPtr<Link> link = new mock_Link(expect_visit); // Now, register our Link to be notified. nsCOMPtr<IHistory> history = do_get_IHistory(); nsresult rv = history->RegisterVisitedCallback(testURI, link); do_check_success(rv); - link.forget(); // It will release itself when notified. // Note: test will continue upon notification. } void test_unvisited_does_not_notify_part2() { using namespace test_unvisited_does_not_notify; @@ -231,18 +230,16 @@ test_same_uri_notifies_both() // Now, register our Link to be notified. nsCOMPtr<IHistory> history = do_get_IHistory(); nsresult rv = history->RegisterVisitedCallback(testURI, link1); do_check_success(rv); rv = history->RegisterVisitedCallback(testURI, link2); do_check_success(rv); - link1.forget(); // It will release itself when notified. - link2.forget(); // It will release itself when notified. // Note: test will continue upon notification. } void test_unregistered_visited_does_not_notify() { // This test must have a test that has a successful notification after it. // The Link would have been notified by now if we were buggy and notified @@ -283,17 +280,16 @@ test_new_visit_notifies_waiting_Link() nsCOMPtr<nsIURI> testURI = new_test_uri(); nsCOMPtr<IHistory> history = do_get_IHistory(); nsresult rv = history->RegisterVisitedCallback(testURI, link); do_check_success(rv); // Add ourselves to history. addURI(testURI); - link.forget(); // It will release itself when notified. // Note: test will continue upon notification. } void test_RegisterVisitedCallback_returns_before_notifying() { // Add a URI so that it's already in history. nsCOMPtr<nsIURI> testURI = new_test_uri(); @@ -397,17 +393,16 @@ test_observer_topic_dispatched() nsresult rv = visitedURI->Equals(notVisitedURI, &urisEqual); do_check_success(rv); do_check_false(urisEqual); addURI(visitedURI); // Need two Link objects as well - one for each URI. nsRefPtr<Link> visitedLink = new mock_Link(expect_visit, false); nsRefPtr<Link> visitedLinkCopy = visitedLink; - visitedLinkCopy.forget(); // It will release itself when notified. nsRefPtr<Link> notVisitedLink = new mock_Link(expect_no_visit); // Add the right observers for the URIs to check results. bool visitedNotified = false; nsCOMPtr<nsIObserver> visitedObs = new statusObserver(visitedURI, true, visitedNotified); bool notVisitedNotified = false; nsCOMPtr<nsIObserver> unvisitedObs =
--- a/toolkit/components/places/tests/head_common.js +++ b/toolkit/components/places/tests/head_common.js @@ -30,17 +30,17 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -const CURRENT_SCHEMA_VERSION = 13; +const CURRENT_SCHEMA_VERSION = 14; const NS_APP_USER_PROFILE_50_DIR = "ProfD"; const NS_APP_PROFILE_DIR_STARTUP = "ProfDS"; const NS_APP_BOOKMARKS_50_FILE = "BMarks"; // Shortcuts to transitions type. const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK; const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
--- a/toolkit/components/places/tests/migration/test_current_from_v10.js +++ b/toolkit/components/places/tests/migration/test_current_from_v10.js @@ -290,16 +290,17 @@ function test_final_state() do_check_true(stmt.executeStep()); // WAL journal mode should be set on this database. do_check_eq(stmt.getString(0).toLowerCase(), "wal"); stmt.finalize(); } do_check_true(db.indexExists("moz_bookmarks_guid_uniqueindex")); do_check_true(db.indexExists("moz_places_guid_uniqueindex")); + do_check_true(db.indexExists("moz_favicons_guid_uniqueindex")); do_check_eq(db.schemaVersion, CURRENT_SCHEMA_VERSION); db.close(); run_next_test(); } ////////////////////////////////////////////////////////////////////////////////
--- a/toolkit/components/places/tests/unit/test_favicons.js +++ b/toolkit/components/places/tests/unit/test_favicons.js @@ -83,16 +83,27 @@ function check_oversized_icon_data(iconN // Compare thet expected data to the actual data. do_check_eq("image/png", outMimeType); if (!skipContent) { do_check_true(compareArrays(expectedData, outData)); } } +function check_page_has_no_favicon(pageURI) { + try { + PlacesUtils.favicons.getFaviconForPage(pageURI); + do_throw("Page has a favicon!"); + } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { + // Page should have no favicon. + } catch (ex) { + do_throw("Unexpected exception " + ex); + } +} + /* * Done with utilities! On to the tests. */ function run_test() { run_next_test(); } add_test(function test_storing_a_normal_16x16_icon() { @@ -343,8 +354,150 @@ add_test(function test_getFaviconData_on do_check_eq(outMimeType.value, "image/png"); // Read in the icon and compare it to what the API returned above. let istream = NetUtil.newChannel(PlacesUtils.favicons.defaultFavicon).open(); let expectedData = readInputStreamData(istream); do_check_true(compareArrays(outData, expectedData)); run_next_test(); }); + +/* + * Retrieve the GUID for a favicon URI. For now we'll do this through SQL. + * Provide a mozIStorageStatementCallback, such as a SingleGUIDCallback. + */ +function guidForFaviconURI(iconURIString, cb) { + let query = "SELECT guid FROM moz_favicons WHERE url = :url"; + let stmt = cb.statement = DBConn().createAsyncStatement(query); + stmt.params.url = iconURIString; + stmt.executeAsync(cb); + stmt.finalize(); +} + +/* + * A mozIStorageStatementCallback for single GUID results. + * Pass in a callback function, which will be invoked on completion. + * Ensures that only a single GUID is returned. + */ +function SingleGUIDCallback(cb) { + this.called = 0; + this.cb = cb; +} +SingleGUIDCallback.prototype = { + _guid: null, + handleCompletion: function handleCompletion(reason) { + do_log_info("Completed single GUID callback."); + do_check_eq(this.called, 1); + this.cb(this._guid); + }, + handleError: function handleError(err) { + do_throw(err); + }, + handleResult: function handleResult(resultSet) { + this.called++; + this._guid = resultSet.getNextRow().getResultByName("guid"); + do_log_info("Retrieved GUID is " + this._guid); + do_check_true(!!this._guid); + do_check_valid_places_guid(this._guid); + do_check_eq(null, resultSet.getNextRow()); // No more rows. + } +}; + +function insertToolbarBookmark(uri, title) { + PlacesUtils.bookmarks.insertBookmark( + PlacesUtils.toolbarFolderId, + uri, + PlacesUtils.bookmarks.DEFAULT_INDEX, + title + ); +} + +add_test(function test_insert_synchronous_mints_guid() { + do_log_info("Test that synchronously inserting a favicon results in a " + + "record with a new GUID."); + + let testURI = "http://test.com/sync/"; + let testIconURI = "http://test.com/favicon.ico"; + let pageURI = NetUtil.newURI(testURI); + + // No icon to start with. + check_page_has_no_favicon(pageURI); + + // Add a page with a bookmark. + insertToolbarBookmark(pageURI, "Test page"); + + // Set a favicon for the page. + PlacesUtils.favicons.setFaviconUrlForPage( + pageURI, NetUtil.newURI(testIconURI) + ); + + // Check that the URI has been set correctly. + do_check_eq(PlacesUtils.favicons.getFaviconForPage(pageURI).spec, + testIconURI); + + guidForFaviconURI(testIconURI, new SingleGUIDCallback(run_next_test)); +}); + +add_test(function test_insert_asynchronous_mints_guid() { + do_log_info("Test that asynchronously inserting a favicon results in a " + + "record with a new GUID."); + + let testURI = "http://test.com/async/"; + let iconURI = NetUtil.newURI(do_get_file("favicon-normal32.png")); + let pageURI = NetUtil.newURI(testURI); + + // No icon to start with. + check_page_has_no_favicon(pageURI); + + // Add a page with a bookmark. + insertToolbarBookmark(pageURI, "Other test page"); + + // Set a favicon for the page. + let faviconDataCallback = { + onFaviconDataAvailable: function (uri, len, data, mimeType) { + do_check_true(iconURI.equals(uri)); + + // Make sure there's a valid GUID. + guidForFaviconURI(iconURI.spec, new SingleGUIDCallback(run_next_test)); + } + }; + + let forceReload = false; + do_log_info("Asynchronously setting page favicon."); + PlacesUtils.favicons.setAndFetchFaviconForPage( + pageURI, iconURI, forceReload, faviconDataCallback + ); +}); + +add_test(function test_insert_asynchronous_update_preserves_guid() { + do_log_info("Test that asynchronously inserting an existing favicon leaves " + + "the GUID unchanged."); + + let testURI = "http://test.com/async/"; + let iconURI = NetUtil.newURI(do_get_file("favicon-normal32.png")); + let pageURI = NetUtil.newURI(testURI); + + guidForFaviconURI(iconURI.spec, new SingleGUIDCallback(function (guid) { + // Set a favicon for the page... again. + let faviconDataCallback = { + onFaviconDataAvailable: function (uri, len, data, mimeType) { + do_check_true(iconURI.equals(uri)); + + // Make sure there's a valid GUID. + guidForFaviconURI(iconURI.spec, new SingleGUIDCallback(function (again) { + do_check_eq(guid, again); + run_next_test(); + })); + } + }; + + let forceReload = true; + do_log_info("Asynchronously re-setting page favicon."); + PlacesUtils.favicons.setAndFetchFaviconForPage( + pageURI, iconURI, forceReload, faviconDataCallback + ); + })); +}); + +/* + * TODO: need a test for async write that modifies a GUID for a favicon. + * This will come later, when there's an API that actually does that! + */
--- a/toolkit/components/startup/tests/browser/browser_bug511456.js +++ b/toolkit/components/startup/tests/browser/browser_bug511456.js @@ -83,16 +83,17 @@ var Watcher = { return this; throw Components.results.NS_ERROR_NO_INTERFACE; } } function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); Services.wm.addListener(Watcher); var win2 = window.openDialog(location, "", "chrome,all,dialog=no", "about:blank"); win2.addEventListener("load", function() { win2.removeEventListener("load", arguments.callee, false); gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
--- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -212,29 +212,37 @@ JSHistogram_Add(JSContext *cx, uintN arg } if (!JS_ValueToECMAInt32(cx, v, &value)) { return JS_FALSE; } if (TelemetryImpl::CanRecord()) { JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return JS_FALSE; + } + Histogram *h = static_cast<Histogram*>(JS_GetPrivate(cx, obj)); if (h->histogram_type() == Histogram::BOOLEAN_HISTOGRAM) h->Add(!!value); else h->Add(value); } return JS_TRUE; } JSBool JSHistogram_Snapshot(JSContext *cx, uintN argc, jsval *vp) { JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return JS_FALSE; + } + Histogram *h = static_cast<Histogram*>(JS_GetPrivate(cx, obj)); JSObject *snapshot = JS_NewObject(cx, NULL, NULL, NULL); if (!snapshot) return JS_FALSE; JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(snapshot)); return ReflectHistogramSnapshot(cx, snapshot, h); }
--- a/toolkit/components/telemetry/TelemetryHistograms.h +++ b/toolkit/components/telemetry/TelemetryHistograms.h @@ -157,16 +157,34 @@ HISTOGRAM(HTTP_REQUEST_PER_PAGE_FROM_CAC _HTTP_HIST(HTTP_##prefix##_REVALIDATION, labelprefix "Positive cache validation time (ms)") \ _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD, labelprefix "Overall load time - all (ms)") \ _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD_CACHED, labelprefix "Overall load time - cache hits (ms)") \ _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD_NET, labelprefix "Overall load time - network (ms)") \ HTTP_HISTOGRAMS(PAGE, "page: ") HTTP_HISTOGRAMS(SUB, "subitem: ") +HISTOGRAM(SPDY_PARALLEL_STREAMS, 1, 1000, 50, EXPONENTIAL, "SPDY: Streams concurrent active per connection") +HISTOGRAM(SPDY_TOTAL_STREAMS, 1, 100000, 50, EXPONENTIAL, "SPDY: Streams created per connection") +HISTOGRAM(SPDY_SERVER_INITIATED_STREAMS, 1, 100000, 250, EXPONENTIAL, "SPDY: Streams recevied per connection") +HISTOGRAM(SPDY_CHUNK_RECVD, 1, 1000, 100, EXPONENTIAL, "SPDY: Recvd Chunk Size (rounded to KB)") +HISTOGRAM(SPDY_SYN_SIZE, 20, 20000, 50, EXPONENTIAL, "SPDY: SYN Frame Header Size") +HISTOGRAM(SPDY_SYN_RATIO, 1, 99, 20, LINEAR, "SPDY: SYN Frame Header Ratio (lower better)") +HISTOGRAM(SPDY_SYN_REPLY_SIZE, 16, 20000, 50, EXPONENTIAL, "SPDY: SYN Reply Header Size") +HISTOGRAM(SPDY_SYN_REPLY_RATIO, 1, 99, 20, LINEAR, "SPDY: SYN Reply Header Ratio (lower better)") +HISTOGRAM(SPDY_NPN_CONNECT, 0, 1, 2, BOOLEAN, "SPDY: NPN Negotiated") + +HISTOGRAM(SPDY_SETTINGS_UL_BW, 1, 10000, 100, EXPONENTIAL, "SPDY: Settings Upload Bandwidth") +HISTOGRAM(SPDY_SETTINGS_DL_BW, 1, 10000, 100, EXPONENTIAL, "SPDY: Settings Download Bandwidth") +HISTOGRAM(SPDY_SETTINGS_RTT, 1, 1000, 100, EXPONENTIAL, "SPDY: Settings RTT") +HISTOGRAM(SPDY_SETTINGS_MAX_STREAMS, 1, 5000, 100, EXPONENTIAL, "SPDY: Settings Max Streams parameter") +HISTOGRAM(SPDY_SETTINGS_CWND, 1, 500, 50, EXPONENTIAL, "SPDY: Settings CWND (packets)") +HISTOGRAM(SPDY_SETTINGS_RETRANS, 1, 100, 50, EXPONENTIAL, "SPDY: Retransmission Rate") +HISTOGRAM(SPDY_SETTINGS_IW, 1, 1000, 50, EXPONENTIAL, "SPDY: Settings IW (rounded to KB)") + #undef _HTTP_HIST #undef HTTP_HISTOGRAMS HISTOGRAM(HTTP_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Cache Hit, Reval, Failed-Reval, Miss") HISTOGRAM(HTTP_DISK_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Disk Cache Hit, Reval, Failed-Reval, Miss") HISTOGRAM(HTTP_MEMORY_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Memory Cache Hit, Reval, Failed-Reval, Miss") HISTOGRAM(HTTP_OFFLINE_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Offline Cache Hit, Reval, Failed-Reval, Miss") HISTOGRAM(CACHE_DEVICE_SEARCH, 1, 100, 100, LINEAR, "Time to search cache (ms)") @@ -212,16 +230,19 @@ HISTOGRAM(MOZ_SQLITE_URLCLASSIFIER_WRITE HISTOGRAM(MOZ_SQLITE_WEBAPPS_WRITE_B, 1, 32768, 3, LINEAR, "SQLite write (bytes)") HISTOGRAM(MOZ_SQLITE_OTHER_WRITE_B, 1, 32768, 3, LINEAR, "SQLite write (bytes)") HISTOGRAM(MOZ_STORAGE_ASYNC_REQUESTS_MS, 1, 32768, 20, EXPONENTIAL, "mozStorage async requests completion (ms)") HISTOGRAM_BOOLEAN(MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, "mozStorage async requests success") HISTOGRAM(STARTUP_MEASUREMENT_ERRORS, 1, mozilla::StartupTimeline::MAX_EVENT_ID, mozilla::StartupTimeline::MAX_EVENT_ID + 1, LINEAR, "Flags errors in startup calculation()") HISTOGRAM(NETWORK_DISK_CACHE_OPEN, 1, 10000, 10, EXPONENTIAL, "Time spent opening disk cache (ms)") HISTOGRAM(NETWORK_DISK_CACHE_TRASHRENAME, 1, 10000, 10, EXPONENTIAL, "Time spent renaming bad Cache to Cache.Trash (ms)") HISTOGRAM(NETWORK_DISK_CACHE_DELETEDIR, 1, 10000, 10, EXPONENTIAL, "Time spent deleting disk cache (ms)") +HISTOGRAM(NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN, 1, 10000, 10, EXPONENTIAL, "Time spent during showdown stopping thread deleting old disk cache (ms)") +HISTOGRAM(NETWORK_DISK_CACHE_SHUTDOWN, 1, 10000, 10, EXPONENTIAL, "Total Time spent (ms) during disk cache showdown") +HISTOGRAM(NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE, 1, 10000, 10, EXPONENTIAL, "Time spent (ms) during showdown deleting disk cache for 'clear private data' option") /** * Url-Classifier telemetry */ #ifdef MOZ_URL_CLASSIFIER HISTOGRAM(URLCLASSIFIER_PS_FILELOAD_TIME, 1, 1000, 10, EXPONENTIAL, "Time spent loading PrefixSet from file (ms)") HISTOGRAM(URLCLASSIFIER_PS_FALLOCATE_TIME, 1, 1000, 10, EXPONENTIAL, "Time spent fallocating PrefixSet (ms)") HISTOGRAM(URLCLASSIFIER_PS_CONSTRUCT_TIME, 1, 5000, 15, EXPONENTIAL, "Time spent constructing PrefixSet from DB (ms)") @@ -254,10 +275,27 @@ HISTOGRAM(THUNDERBIRD_CONVERSATIONS_TIME HISTOGRAM(THUNDERBIRD_INDEXING_RATE_MSG_PER_S, 1, 100, 20, LINEAR, "Gloda: indexing rate (message/s)") #endif HISTOGRAM_BOOLEAN(INNERWINDOWS_WITH_MUTATION_LISTENERS, "Deleted or to-be-reused innerwindow which has had mutation event listeners.") HISTOGRAM(XUL_REFLOW_MS, 1, 3000, 10, EXPONENTIAL, "xul reflows") HISTOGRAM(XUL_INITIAL_FRAME_CONSTRUCTION, 1, 3000, 10, EXPONENTIAL, "initial xul frame construction") HISTOGRAM_BOOLEAN(XMLHTTPREQUEST_ASYNC_OR_SYNC, "Type of XMLHttpRequest, async or sync") +/** + * DOM Storage telemetry. + */ +#define DOMSTORAGE_HISTOGRAM(PREFIX, TYPE, TYPESTRING, DESCRIPTION) \ + HISTOGRAM(PREFIX ## DOMSTORAGE_ ## TYPE ## _SIZE_BYTES, \ + 1024, 32768, 10, EXPONENTIAL, "DOM storage: size of " TYPESTRING "s stored in " DESCRIPTION "Storage") +#define DOMSTORAGE_KEY_VAL_SIZE(PREFIX, DESCRIPTION) \ + DOMSTORAGE_HISTOGRAM(PREFIX, KEY, "key", DESCRIPTION) \ + DOMSTORAGE_HISTOGRAM(PREFIX, VALUE, "value", DESCRIPTION) + +DOMSTORAGE_KEY_VAL_SIZE(GLOBAL, "global") +DOMSTORAGE_KEY_VAL_SIZE(LOCAL, "local") +DOMSTORAGE_KEY_VAL_SIZE(SESSION, "session") + +#undef DOMSTORAGE_KEY_VAL_SIZE +#undef DOMSTORAGE_HISTOGRAM + #undef HISTOGRAM_BOOLEAN
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp +++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp @@ -57,17 +57,16 @@ #include "nsIObserverService.h" #include "nsIPermissionManager.h" #include "nsIPrefBranch.h" #include "nsIPrefBranch2.h" #include "nsIPrefService.h" #include "nsIProperties.h" #include "nsToolkitCompsCID.h" #include "nsIUrlClassifierUtils.h" -#include "nsIRandomGenerator.h" #include "nsUrlClassifierDBService.h" #include "nsUrlClassifierUtils.h" #include "nsUrlClassifierProxies.h" #include "nsURILoader.h" #include "nsString.h" #include "nsReadableUtils.h" #include "nsTArray.h" #include "nsNetUtil.h" @@ -483,19 +482,23 @@ public: // Read a certain number of rows adjacent to the requested rowid that // don't have complete hash data. nsresult ReadNoiseEntries(PRInt64 rowID, PRUint32 numRequested, bool before, nsTArray<nsUrlClassifierEntry> &entries); + // Ask the db for a random number. This is temporary, and should be + // replaced with nsIRandomGenerator when 419739 is fixed. + nsresult RandomNumber(PRInt64 *randomNum); // Return an array with all Prefixes known nsresult ReadPrefixes(nsTArray<PRUint32>& array, PRUint32 aKey); + protected: nsresult ReadEntries(mozIStorageStatement *statement, nsTArray<nsUrlClassifierEntry>& entries); nsUrlClassifierDBServiceWorker *mWorker; nsCOMPtr<mozIStorageConnection> mConnection; nsCOMPtr<mozIStorageStatement> mLookupWithIDStatement; @@ -504,16 +507,17 @@ protected: nsCOMPtr<mozIStorageStatement> mDeleteStatement; nsCOMPtr<mozIStorageStatement> mExpireStatement; nsCOMPtr<mozIStorageStatement> mPartialEntriesStatement; nsCOMPtr<mozIStorageStatement> mPartialEntriesAfterStatement; nsCOMPtr<mozIStorageStatement> mLastPartialEntriesStatement; nsCOMPtr<mozIStorageStatement> mPartialEntriesBeforeStatement; + nsCOMPtr<mozIStorageStatement> mRandomStatement; nsCOMPtr<mozIStorageStatement> mAllPrefixStatement; }; nsresult nsUrlClassifierStore::Init(nsUrlClassifierDBServiceWorker *worker, mozIStorageConnection *connection, const nsACString& entriesName) { @@ -562,17 +566,21 @@ nsUrlClassifierStore::Init(nsUrlClassifi rv = mConnection->CreateStatement (NS_LITERAL_CSTRING("SELECT * FROM ") + entriesName + NS_LITERAL_CSTRING(" WHERE id < ?1 AND complete_data ISNULL" " ORDER BY id DESC LIMIT ?2"), getter_AddRefs(mPartialEntriesBeforeStatement)); NS_ENSURE_SUCCESS(rv, rv); rv = mConnection->CreateStatement - (NS_LITERAL_CSTRING("SELECT domain, partial_data, complete_data FROM ") + (NS_LITERAL_CSTRING("SELECT abs(random())"), + getter_AddRefs(mRandomStatement)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT domain, partial_data, complete_data FROM ") + entriesName, getter_AddRefs(mAllPrefixStatement)); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } void @@ -584,16 +592,17 @@ nsUrlClassifierStore::Close() mUpdateStatement = nsnull; mDeleteStatement = nsnull; mExpireStatement = nsnull; mPartialEntriesStatement = nsnull; mPartialEntriesAfterStatement = nsnull; mPartialEntriesBeforeStatement = nsnull; mLastPartialEntriesStatement = nsnull; + mRandomStatement = nsnull; mAllPrefixStatement = nsnull; mConnection = nsnull; } bool @@ -768,16 +777,31 @@ nsUrlClassifierStore::ReadNoiseEntries(P mozStorageStatementScoper wraparoundScoper(wraparoundStatement); rv = wraparoundStatement->BindInt32ByIndex(0, numRequested - numRead); NS_ENSURE_SUCCESS(rv, rv); return ReadEntries(wraparoundStatement, entries); } +nsresult +nsUrlClassifierStore::RandomNumber(PRInt64 *randomNum) +{ + mozStorageStatementScoper randScoper(mRandomStatement); + bool exists; + nsresult rv = mRandomStatement->ExecuteStep(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_NOT_AVAILABLE; + + *randomNum = mRandomStatement->AsInt64(0); + + return NS_OK; +} + // ------------------------------------------------------------------------- // nsUrlClassifierAddStore class implementation // This class accesses the moz_classifier table. class nsUrlClassifierAddStore: public nsUrlClassifierStore { public: nsUrlClassifierAddStore() {}; @@ -1762,26 +1786,19 @@ nsresult nsUrlClassifierDBServiceWorker::AddNoise(PRInt64 nearID, PRInt32 count, nsTArray<nsUrlClassifierLookupResult>& results) { if (count < 1) { return NS_OK; } - nsCOMPtr<nsIRandomGenerator> rg = - do_GetService("@mozilla.org/security/random-generator;1"); - NS_ENSURE_STATE(rg); - - PRInt32 randomNum; - PRUint8 *temp; - nsresult rv = rg->GenerateRandomBytes(sizeof(randomNum), &temp); + PRInt64 randomNum; + nsresult rv = mMainStore.RandomNumber(&randomNum); NS_ENSURE_SUCCESS(rv, rv); - memcpy(&randomNum, temp, sizeof(randomNum)); - NS_Free(temp); PRInt32 numBefore = randomNum % count; nsTArray<nsUrlClassifierEntry> noiseEntries; rv = mMainStore.ReadNoiseEntries(nearID, numBefore, true, noiseEntries); NS_ENSURE_SUCCESS(rv, rv); rv = mMainStore.ReadNoiseEntries(nearID, count - numBefore, false, noiseEntries); @@ -3073,18 +3090,23 @@ nsUrlClassifierDBServiceWorker::FinishSt mServerMAC.get(), clientMAC.get())); mUpdateStatus = NS_ERROR_FAILURE; } PRIntervalTime updateTime = PR_IntervalNow() - mUpdateStartTime; if (PR_IntervalToSeconds(updateTime) >= static_cast<PRUint32>(gWorkingTimeThreshold)) { // We've spent long enough working that we should commit what we // have and hold off for a bit. - ApplyUpdate(); - + nsresult rv = ApplyUpdate(); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_FILE_CORRUPTED) { + ResetDatabase(); + } + return rv; + } nextStreamDelay = gDelayTime * 1000; } } mUpdateObserver->StreamFinished(mUpdateStatus, static_cast<PRUint32>(nextStreamDelay)); ResetStream(); @@ -3186,17 +3208,23 @@ nsUrlClassifierDBServiceWorker::FinishUp NS_ENSURE_STATE(mUpdateObserver); // We need to get the error code before ApplyUpdate, because it might // close/open the connection. PRInt32 errcode = SQLITE_OK; if (mConnection) mConnection->GetLastError(&errcode); - ApplyUpdate(); + nsresult rv = ApplyUpdate(); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_FILE_CORRUPTED) { + ResetDatabase(); + } + return rv; + } if (NS_SUCCEEDED(mUpdateStatus)) { mUpdateObserver->UpdateSuccess(mUpdateWait); } else { mUpdateObserver->UpdateError(mUpdateStatus); } // It's important that we only reset the database on an update @@ -3448,17 +3476,22 @@ nsUrlClassifierDBServiceWorker::OpenDb() mConnection = connection; mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); LOG(("loading Prefix Set\n")); rv = LoadPrefixSet(mPSFile); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_FILE_CORRUPTED) { + ResetDatabase(); + } + return rv; + } return NS_OK; } // We have both a prefix and a domain. Drop the domain, but // hash the domain, the prefix and a random value together, // ensuring any collisions happens at a different points for // different users. @@ -3556,16 +3589,21 @@ nsresult nsUrlClassifierStore::ReadPrefi } PRUint32 keyedVal; nsresult rv = KeyedHash(prefixval, domainval, aKey, &keyedVal); NS_ENSURE_SUCCESS(rv, rv); array.AppendElement(keyedVal); pcnt++; + // Normal DB size is about 500k entries. If we are getting 10x + // as much, the database must be corrupted. + if (pcnt > 5000000) { + return NS_ERROR_FILE_CORRUPTED; + } } LOG(("SB prefixes: %d fulldomain: %d\n", pcnt, fcnt)); #if defined(PR_LOGGING) if (LOG_ENABLED()) { PRIntervalTime clockEnd = PR_IntervalNow(); LOG(("Gathering took %dms\n", @@ -3643,17 +3681,18 @@ nsUrlClassifierDBServiceWorker::LoadPref if (exists) { Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_PS_FILELOAD_TIME> timer; LOG(("stored PrefixSet exists, loading from disk")); rv = mPrefixSet->LoadFromFile(aFile); } if (!exists || NS_FAILED(rv)) { LOG(("no (usable) stored PrefixSet found, constructing from store")); - ConstructPrefixSet(); + rv = ConstructPrefixSet(); + NS_ENSURE_SUCCESS(rv, rv); } #ifdef DEBUG PRUint32 size = 0; rv = mPrefixSet->SizeOfIncludingThis(&size); LOG(("SB tree done, size = %d bytes\n", size)); NS_ENSURE_SUCCESS(rv, rv); #endif
--- a/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp +++ b/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp @@ -307,18 +307,25 @@ nsUrlClassifierPrefixSet::Contains(PRUin PRUint32 i = BinSearch(0, mIndexPrefixes.Length() - 1, target); if (mIndexPrefixes[i] > target && i > 0) { i--; } // Now search through the deltas for the target. PRUint32 diff = target - mIndexPrefixes[i]; PRUint32 deltaIndex = mIndexStarts[i]; + PRUint32 deltaSize = mDeltas.Length(); PRUint32 end = (i + 1 < mIndexStarts.Length()) ? mIndexStarts[i+1] - : mDeltas.Length(); + : deltaSize; + + // Sanity check the read values + if (end > deltaSize) { + return NS_ERROR_FILE_CORRUPTED; + } + while (diff > 0 && deltaIndex < end) { diff -= mDeltas[deltaIndex]; deltaIndex++; } if (diff == 0) { *aFound = true; } @@ -397,49 +404,55 @@ nsUrlClassifierPrefixSet::Probe(PRUint32 nsresult nsUrlClassifierPrefixSet::LoadFromFd(AutoFDClose & fileFd) { PRUint32 magic; PRInt32 read; read = PR_Read(fileFd, &magic, sizeof(PRUint32)); - NS_ENSURE_TRUE(read > 0, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(read == sizeof(PRUint32), NS_ERROR_FAILURE); if (magic == PREFIXSET_VERSION_MAGIC) { PRUint32 indexSize; PRUint32 deltaSize; read = PR_Read(fileFd, &mRandomKey, sizeof(PRUint32)); - NS_ENSURE_TRUE(read > 0, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(read == sizeof(PRUint32), NS_ERROR_FILE_CORRUPTED); read = PR_Read(fileFd, &indexSize, sizeof(PRUint32)); - NS_ENSURE_TRUE(read > 0, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(read == sizeof(PRUint32), NS_ERROR_FILE_CORRUPTED); read = PR_Read(fileFd, &deltaSize, sizeof(PRUint32)); - NS_ENSURE_TRUE(read > 0, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(read == sizeof(PRUint32), NS_ERROR_FILE_CORRUPTED); if (indexSize == 0) { LOG(("stored PrefixSet is empty!")); return NS_ERROR_FAILURE; } + if (deltaSize > (indexSize * DELTAS_LIMIT)) { + return NS_ERROR_FILE_CORRUPTED; + } + nsTArray<PRUint32> mNewIndexPrefixes; nsTArray<PRUint32> mNewIndexStarts; nsTArray<PRUint16> mNewDeltas; mNewIndexStarts.SetLength(indexSize); mNewIndexPrefixes.SetLength(indexSize); mNewDeltas.SetLength(deltaSize); - read = PR_Read(fileFd, mNewIndexPrefixes.Elements(), indexSize*sizeof(PRUint32)); - NS_ENSURE_TRUE(read > 0, NS_ERROR_FAILURE); - read = PR_Read(fileFd, mNewIndexStarts.Elements(), indexSize*sizeof(PRUint32)); - NS_ENSURE_TRUE(read > 0, NS_ERROR_FAILURE); + PRInt32 toRead = indexSize*sizeof(PRUint32); + read = PR_Read(fileFd, mNewIndexPrefixes.Elements(), toRead); + NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED); + read = PR_Read(fileFd, mNewIndexStarts.Elements(), toRead); + NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED); if (deltaSize > 0) { - read = PR_Read(fileFd, mNewDeltas.Elements(), deltaSize*sizeof(PRUint16)); - NS_ENSURE_TRUE(read > 0, NS_ERROR_FAILURE); + toRead = deltaSize*sizeof(PRUint16); + read = PR_Read(fileFd, mNewDeltas.Elements(), toRead); + NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED); } MutexAutoLock lock(mPrefixSetLock); mIndexPrefixes.SwapElements(mNewIndexPrefixes); mIndexStarts.SwapElements(mNewIndexStarts); mDeltas.SwapElements(mNewDeltas);
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd +++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd @@ -1,10 +1,15 @@ <!ENTITY addons.windowTitle "Add-ons Manager"> + <!ENTITY search.placeholder "Search all add-ons"> +<!-- LOCALIZATION NOTE (search.commandKey): + The search command key should match findOnCmd.commandkey from browser.dtd --> +<!ENTITY search.commandkey "f"> + <!ENTITY loading.label "Loading…"> <!ENTITY listEmpty.installed.label "You don't have any add-ons of this type installed"> <!ENTITY listEmpty.availableUpdates.label "No updates found"> <!ENTITY listEmpty.recentUpdates.label "You haven't recently updated any add-ons"> <!ENTITY listEmpty.findUpdates.label "Check For Updates"> <!ENTITY listEmpty.search.label "Could not find any matching add-ons"> <!ENTITY listEmpty.button.label "Learn more about add-ons"> <!ENTITY installAddonFromFile.label "Install Add-on From File…">
--- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -16,16 +16,17 @@ # # The Initial Developer of the Original Code is # the Mozilla Foundation. # Portions created by the Initial Developer are Copyright (C) 2009 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Dave Townsend <dtownsend@oxymoronical.com> +# Blair McBride <bmcbride@mozilla.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -45,18 +46,21 @@ const Cr = Components.results; const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled"; const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion"; const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; +// Note: This has to be kept in sync with the same constant in AddonRepository.jsm const STRICT_COMPATIBILITY_DEFAULT = true; +const TOOLKIT_ID = "toolkit@mozilla.org"; + const VALID_TYPES_REGEXP = /^[\w\-]+$/; Components.utils.import("resource://gre/modules/Services.jsm"); var EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ]; const CATEGORY_PROVIDER_MODULE = "addon-provider-module"; @@ -230,16 +234,77 @@ AddonScreenshot.prototype = { caption: null, // Returns the screenshot URL, defaulting to the empty string toString: function() { return this.url || ""; } } + +/** + * This represents a compatibility override for an addon. + * + * @param aType + * Overrride type - "compatible" or "incompatible" + * @param aMinVersion + * Minimum version of the addon to match + * @param aMaxVersion + * Maximum version of the addon to match + * @param aAppID + * Application ID used to match appMinVersion and appMaxVersion + * @param aAppMinVersion + * Minimum version of the application to match + * @param aAppMaxVersion + * Maximum version of the application to match + */ +function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID, + aAppMinVersion, aAppMaxVersion) { + this.type = aType; + this.minVersion = aMinVersion; + this.maxVersion = aMaxVersion; + this.appID = aAppID; + this.appMinVersion = aAppMinVersion; + this.appMaxVersion = aAppMaxVersion; +} + +AddonCompatibilityOverride.prototype = { + /** + * Type of override - "incompatible" or "compatible". + * Only "incompatible" is supported for now. + */ + type: null, + + /** + * Min version of the addon to match. + */ + minVersion: null, + + /** + * Max version of the addon to match. + */ + maxVersion: null, + + /** + * Application ID to match. + */ + appID: null, + + /** + * Min version of the application to match. + */ + appMinVersion: null, + + /** + * Max version of the application to match. + */ + appMaxVersion: null +}; + + /** * A type of add-on, used by the UI to determine how to display different types * of add-ons. * * @param aId * The add-on type ID * @param aLocaleURI * The URI of a localized properties file to get the displayable name @@ -547,53 +612,61 @@ var AddonManagerInternal = { * Performs a background update check by starting an update for all add-ons * that can be updated. */ backgroundUpdateCheck: function AMI_backgroundUpdateCheck() { if (!Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED)) return; Services.obs.notifyObservers(null, "addons-background-update-start", null); - let pendingUpdates = 1; + let pendingUpdates = 0; function notifyComplete() { - if (--pendingUpdates == 0) - Services.obs.notifyObservers(null, "addons-background-update-complete", null); + if (--pendingUpdates == 0) { + Services.obs.notifyObservers(null, + "addons-background-update-complete", + null); + } } let scope = {}; Components.utils.import("resource://gre/modules/AddonRepository.jsm", scope); Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope); scope.LightweightThemeManager.updateCurrentTheme(); + pendingUpdates++; this.getAllAddons(function getAddonsCallback(aAddons) { - pendingUpdates++; + // Repopulate repository cache first, to ensure compatibility overrides + // are up to date before checking for addon updates. var ids = [a.id for each (a in aAddons)]; - scope.AddonRepository.repopulateCache(ids, notifyComplete); + scope.AddonRepository.repopulateCache(ids, function BUC_repopulateCacheCallback() { + AddonManagerInternal.updateAddonRepositoryData(function BUC_updateAddonCallback() { - pendingUpdates += aAddons.length; + pendingUpdates += aAddons.length; - aAddons.forEach(function BUC_forEachCallback(aAddon) { - // Check all add-ons for updates so that any compatibility updates will - // be applied - aAddon.findUpdates({ - onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) { - // Start installing updates when the add-on can be updated and - // background updates should be applied. - if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && - AddonManager.shouldAutoUpdate(aAddon)) { - aInstall.install(); - } - }, - - onUpdateFinished: notifyComplete - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + aAddons.forEach(function BUC_forEachCallback(aAddon) { + // Check all add-ons for updates so that any compatibility updates will + // be applied + aAddon.findUpdates({ + onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) { + // Start installing updates when the add-on can be updated and + // background updates should be applied. + if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && + AddonManager.shouldAutoUpdate(aAddon)) { + aInstall.install(); + } + }, + + onUpdateFinished: notifyComplete + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + + notifyComplete(); + }); }); - - notifyComplete(); }); }, /** * Adds a add-on to the list of detected changes for this startup. If * addStartupChange is called multiple times for the same add-on in the same * startup then only the most recent change will be remembered. * @@ -710,17 +783,41 @@ var AddonManagerInternal = { * their add-ons in response to an application change such as a blocklist * update. */ updateAddonAppDisabledStates: function AMI_updateAddonAppDisabledStates() { this.providers.forEach(function(provider) { callProvider(provider, "updateAddonAppDisabledStates"); }); }, + + /** + * Notifies all providers that the repository has updated its data for + * installed add-ons. + * + * @param aCallback + * Function to call when operation is complete. + */ + updateAddonRepositoryData: function AMI_updateAddonRepositoryData(aCallback) { + if (!aCallback) + throw Components.Exception("Must specify aCallback", + Cr.NS_ERROR_INVALID_ARG); + new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", { + nextObject: function(aCaller, aProvider) { + callProvider(aProvider, + "updateAddonRepositoryData", + null, + aCaller.callNext.bind(aCaller)); + }, + noMoreObjects: function(aCaller) { + safeCall(aCallback); + } + }); + }, /** * Asynchronously gets an AddonInstall for a URL. * * @param aUrl * The url the add-on is located at * @param aCallback * A callback to pass the AddonInstall to * @param aMimetype @@ -1220,16 +1317,18 @@ var AddonManagerPrivate = { callAddonListeners: function AMP_callAddonListeners(aMethod) { AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, arguments); }, AddonAuthor: AddonAuthor, AddonScreenshot: AddonScreenshot, + AddonCompatibilityOverride: AddonCompatibilityOverride, + AddonType: AddonType }; /** * This is the public API that UI and developers should be calling. All methods * just forward to AddonManagerInternal. */ var AddonManager = {
--- a/toolkit/mozapps/extensions/AddonRepository.jsm +++ b/toolkit/mozapps/extensions/AddonRepository.jsm @@ -16,16 +16,17 @@ # # The Initial Developer of the Original Code is mozilla.org # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Dave Townsend <dtownsend@oxymoronical.com> # Ben Parr <bparr@bparr.com> +# Blair McBride <bmcbride@mozilla.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -69,24 +70,30 @@ XPCOMUtils.defineLazyGetter(this, "PREF_ #ifdef MOZ_COMPATIBILITY_NIGHTLY return PREF_CHECK_COMPATIBILITY_BASE + ".nightly"; #else return PREF_CHECK_COMPATIBILITY_BASE + "." + Services.appinfo.version.replace(BRANCH_REGEXP, "$1"); #endif }); +const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; +// Note: This has to be kept in sync with the same constant in AddonManager.jsm +const STRICT_COMPATIBILITY_DEFAULT = true; + const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"; const API_VERSION = "1.5"; const DEFAULT_CACHE_TYPES = "extension,theme,locale,dictionary"; const KEY_PROFILEDIR = "ProfD"; const FILE_DATABASE = "addons.sqlite"; -const DB_SCHEMA = 2; +const DB_SCHEMA = 3; + +const TOOLKIT_ID = "toolkit@mozilla.org"; ["LOG", "WARN", "ERROR"].forEach(function(aName) { this.__defineGetter__(aName, function() { Components.utils.import("resource://gre/modules/AddonLogging.jsm"); LogManager.getLogger("addons.repository", this); return this[aName]; }); @@ -352,16 +359,22 @@ AddonSearchResult.prototype = { /** * True or false depending on whether the add-on is compatible with the * current platform */ isPlatformCompatible: true, /** + * Array of AddonCompatibilityOverride objects, that describe overrides for + * compatibility with an application versions. + **/ + compatibilityOverrides: null, + + /** * True if the add-on has a secure means of updating */ providesUpdatesSecurely: true, /** * The current blocklist state of the add-on */ blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED, @@ -604,18 +617,18 @@ var AddonRepository = { AddonDatabase.delete(aCallback); return; } let self = this; getAddonsToCache(aIds, function(aAddons) { // Completely remove cache if there are no add-ons to cache if (aAddons.length == 0) { - this._addons = null; - this._pendingCallbacks = null; + self._addons = null; + self._pendingCallbacks = null; AddonDatabase.delete(aCallback); return; } self.getAddonsByIDs(aAddons, { searchSucceeded: function(aAddons) { self._addons = {}; aAddons.forEach(function(aAddon) { self._addons[aAddon.id] = aAddon; }); @@ -739,35 +752,53 @@ var AddonRepository = { let params = { API_VERSION : API_VERSION, IDS : ids.map(encodeURIComponent).join(',') }; let url = this._formatURLPref(PREF_GETADDONS_BYIDS, params); let self = this; - function handleResults(aElements, aTotalResults) { + function handleResults(aElements, aTotalResults, aCompatData) { // Don't use this._parseAddons() so that, for example, // incompatible add-ons are not filtered out let results = []; for (let i = 0; i < aElements.length && results.length < self._maxResults; i++) { - let result = self._parseAddon(aElements[i]); + let result = self._parseAddon(aElements[i], null, aCompatData); if (result == null) continue; // Ignore add-on if it wasn't actually requested let idIndex = ids.indexOf(result.addon.id); if (idIndex == -1) continue; results.push(result); // Ignore this add-on from now on ids.splice(idIndex, 1); } + // Include any compatibility overrides for addons not hosted by the + // remote repository. + for each (let addonCompat in aCompatData) { + if (addonCompat.hosted) + continue; + + let addon = new AddonSearchResult(addonCompat.id); + // Compatibility overrides can only be for extensions. + addon.type = "extension"; + addon.compatibilityOverrides = addonCompat.compatRanges; + let result = { + addon: addon, + xpiURL: null, + xpiHash: null + }; + results.push(result); + } + // aTotalResults irrelevant self._reportSuccess(results, -1); } this._beginSearch(url, ids.length, aCallback, handleResults); }, /** @@ -860,54 +891,76 @@ var AddonRepository = { }, // Get descendant by unique tag name. Returns null if not unique tag name. _getUniqueDescendant: function(aElement, aTagName) { let elementsList = aElement.getElementsByTagName(aTagName); return (elementsList.length == 1) ? elementsList[0] : null; }, + // Get direct descendant by unique tag name. + // Returns null if not unique tag name. + _getUniqueDirectDescendant: function(aElement, aTagName) { + let elementsList = Array.filter(aElement.children, + function(aChild) aChild.tagName == aTagName); + return (elementsList.length == 1) ? elementsList[0] : null; + }, + // Parse out trimmed text content. Returns null if text content empty. _getTextContent: function(aElement) { let textContent = aElement.textContent.trim(); return (textContent.length > 0) ? textContent : null; }, // Parse out trimmed text content of a descendant with the specified tag name // Returns null if the parsing unsuccessful. _getDescendantTextContent: function(aElement, aTagName) { let descendant = this._getUniqueDescendant(aElement, aTagName); return (descendant != null) ? this._getTextContent(descendant) : null; }, + // Parse out trimmed text content of a direct descendant with the specified + // tag name. + // Returns null if the parsing unsuccessful. + _getDirectDescendantTextContent: function(aElement, aTagName) { + let descendant = this._getUniqueDirectDescendant(aElement, aTagName); + return (descendant != null) ? this._getTextContent(descendant) : null; + }, + /* * Creates an AddonSearchResult by parsing an <addon> element * * @param aElement * The <addon> element to parse * @param aSkip * Object containing ids and sourceURIs of add-ons to skip. + * @param aCompatData + * Array of parsed addon_compatibility elements to accosiate with the + * resulting AddonSearchResult. Optional. * @return Result object containing the parsed AddonSearchResult, xpiURL and * xpiHash if the parsing was successful. Otherwise returns null. */ - _parseAddon: function(aElement, aSkip) { + _parseAddon: function(aElement, aSkip, aCompatData) { let skipIDs = (aSkip && aSkip.ids) ? aSkip.ids : []; let skipSourceURIs = (aSkip && aSkip.sourceURIs) ? aSkip.sourceURIs : []; let guid = this._getDescendantTextContent(aElement, "guid"); if (guid == null || skipIDs.indexOf(guid) != -1) return null; let addon = new AddonSearchResult(guid); let result = { addon: addon, xpiURL: null, xpiHash: null }; + if (aCompatData && guid in aCompatData) + addon.compatibilityOverrides = aCompatData[guid].compatRanges; + let self = this; for (let node = aElement.firstChild; node; node = node.nextSibling) { if (!(node instanceof Ci.nsIDOMElement)) continue; let localName = node.localName; // Handle case where the wanted string value is located in text content @@ -1090,16 +1143,21 @@ var AddonRepository = { let self = this; let results = []; let checkCompatibility = true; try { checkCompatibility = Services.prefs.getBoolPref(PREF_CHECK_COMPATIBILITY); } catch(e) { } + let strictCompatibility = STRICT_COMPATIBILITY_DEFAULT; + try { + strictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); + } catch (e) {} + function isSameApplication(aAppNode) { return self._getTextContent(aAppNode) == Services.appinfo.ID; } for (let i = 0; i < aElements.length && results.length < this._maxResults; i++) { let element = aElements[i]; let tags = this._getUniqueDescendant(element, "compatible_applications"); @@ -1114,29 +1172,32 @@ var AddonRepository = { let parent = aAppNode.parentNode; let minVersion = self._getDescendantTextContent(parent, "min_version"); let maxVersion = self._getDescendantTextContent(parent, "max_version"); if (minVersion == null || maxVersion == null) return false; let currentVersion = Services.appinfo.version; return (Services.vc.compare(minVersion, currentVersion) <= 0 && - Services.vc.compare(currentVersion, maxVersion) <= 0); + ((!strictCompatibility) || + Services.vc.compare(currentVersion, maxVersion) <= 0)); }); // Ignore add-ons not compatible with this Application if (!compatible) { if (checkCompatibility) continue; if (!Array.some(applications, isSameApplication)) continue; } - // Add-on meets all requirements, so parse out data + // Add-on meets all requirements, so parse out data. + // Don't pass in compatiblity override data, because that's only returned + // in GUID searches, which don't use _parseAddons(). let result = this._parseAddon(element, aSkip); if (result == null) continue; // Ignore add-on missing a required attribute let requiredAttributes = ["id", "name", "version", "type", "creator"]; if (requiredAttributes.some(function(aAttribute) !result.addon[aAttribute])) continue; @@ -1181,16 +1242,93 @@ var AddonRepository = { addon.name, addon.iconURL, addon.version); } else { callback(null); } }); }, + // Parses addon_compatibility nodes, that describe compatibility overrides. + _parseAddonCompatElement: function(aResultObj, aElement) { + let guid = this._getDescendantTextContent(aElement, "guid"); + if (!guid) + return; + + let compat = {id: guid}; + compat.hosted = aElement.getAttribute("hosted") != "false"; + + function findMatchingAppRange(aNodes) { + let toolkitAppRange = null; + for (let i = 0; i < aNodes.length; i++) { + let node = aNodes[i]; + let appID = this._getDescendantTextContent(node, "appID"); + if (appID != Services.appinfo.ID && appID != TOOLKIT_ID) + continue; + + let minVersion = this._getDescendantTextContent(node, "min_version"); + let maxVersion = this._getDescendantTextContent(node, "max_version"); + if (minVersion == null || maxVersion == null) + continue; + + let appRange = { appID: appID, + appMinVersion: minVersion, + appMaxVersion: maxVersion }; + + // Only use Toolkit app ranges if no ranges match the application ID. + if (appID == TOOLKIT_ID) + toolkitAppRange = appRange; + else + return appRange; + } + return toolkitAppRange; + } + + function parseRangeNode(aNode) { + let type = aNode.getAttribute("type"); + // Only "incompatible" (blacklisting) is supported for now. + if (type != "incompatible") + return null; + + let override = new AddonManagerPrivate.AddonCompatibilityOverride(type); + + override.minVersion = this._getDirectDescendantTextContent(aNode, "min_version"); + override.maxVersion = this._getDirectDescendantTextContent(aNode, "max_version"); + + if (!override.minVersion || !override.maxVersion) + return null; + + let appRanges = aNode.querySelectorAll("compatible_applications > application"); + let appRange = findMatchingAppRange.bind(this)(appRanges); + if (!appRange) + return null; + + override.appID = appRange.appID; + override.appMinVersion = appRange.appMinVersion; + override.appMaxVersion = appRange.appMaxVersion; + + return override; + } + + let rangeNodes = aElement.querySelectorAll("version_ranges > version_range"); + compat.compatRanges = Array.map(rangeNodes, parseRangeNode.bind(this)) + .filter(function(aItem) !!aItem); + if (compat.compatRanges.length == 0) + return; + + aResultObj[compat.id] = compat; + }, + + // Parses addon_compatibility elements. + _parseAddonCompatData: function(aElements) { + let compatData = {}; + Array.forEach(aElements, this._parseAddonCompatElement.bind(this, compatData)); + return compatData; + }, + // Begins a new search if one isn't currently executing _beginSearch: function(aURI, aMaxResults, aCallback, aHandleResults) { if (this._searching || aURI == null || aMaxResults <= 0) { aCallback.searchFailed(); return; } this._searching = true; @@ -1222,17 +1360,20 @@ var AddonRepository = { let documentElement = responseXML.documentElement; let elements = documentElement.getElementsByTagName("addon"); let totalResults = elements.length; let parsedTotalResults = parseInt(documentElement.getAttribute("total_results")); // Parsed value of total results only makes sense if >= elements.length if (parsedTotalResults >= totalResults) totalResults = parsedTotalResults; - aHandleResults(elements, totalResults); + let compatElements = documentElement.getElementsByTagName("addon_compatibility"); + let compatData = self._parseAddonCompatData(compatElements); + + aHandleResults(elements, totalResults, compatData); }, false); this._request.send(null); }, // Gets the id's of local add-ons, and the sourceURI's of local installs, // passing the results to aCallback _getLocalAddonIds: function(aCallback) { let self = this; @@ -1266,17 +1407,43 @@ var AddonRepository = { return null; } url = url.replace(/%([A-Z_]+)%/g, function(aMatch, aKey) { return (aKey in aSubstitutions) ? aSubstitutions[aKey] : aMatch; }); return Services.urlFormatter.formatURL(url); + }, + + // Find a AddonCompatibilityOverride that matches a given aAddonVersion and + // application/platform version. + findMatchingCompatOverride: function AR_findMatchingCompatOverride(aAddonVersion, + aCompatOverrides, + aAppVersion, + aPlatformVersion) { + for (let i = 0; i < aCompatOverrides.length; i++) { + let override = aCompatOverrides[i]; + + let appVersion = null; + if (override.appID == TOOLKIT_ID) + appVersion = aPlatformVersion || Services.appinfo.platformVersion; + else + appVersion = aAppVersion || Services.appinfo.version; + + if (Services.vc.compare(override.minVersion, aAddonVersion) <= 0 && + Services.vc.compare(aAddonVersion, override.maxVersion) <= 0 && + Services.vc.compare(override.appMinVersion, appVersion) <= 0 && + Services.vc.compare(appVersion, override.appMaxVersion) <= 0) { + return override; + } + } + return null; } + }; AddonRepository.initialize(); var AddonDatabase = { // true if the database connection has been opened initialized: false, // false if there was an unrecoverable error openning the database databaseOk: true, @@ -1295,16 +1462,21 @@ var AddonDatabase = { getAllDevelopers: "SELECT addon_internal_id, name, url FROM developer " + "ORDER BY addon_internal_id, num", getAllScreenshots: "SELECT addon_internal_id, url, width, height, " + "thumbnailURL, thumbnailWidth, thumbnailHeight, caption " + "FROM screenshot ORDER BY addon_internal_id, num", + getAllCompatOverrides: "SELECT addon_internal_id, type, minVersion, " + + "maxVersion, appID, appMinVersion, appMaxVersion " + + "FROM compatibility_override " + + "ORDER BY addon_internal_id, num", + insertAddon: "INSERT INTO addon VALUES (NULL, :id, :type, :name, :version, " + ":creator, :creatorURL, :description, :fullDescription, " + ":developerComments, :eula, :iconURL, :homepageURL, :supportURL, " + ":contributionURL, :contributionAmount, :averageRating, " + ":reviewCount, :reviewURL, :totalDownloads, :weeklyDownloads, " + ":dailyUsers, :sourceURI, :repositoryStatus, :size, :updateDate)", insertDeveloper: "INSERT INTO developer VALUES (:addon_internal_id, " + @@ -1314,16 +1486,21 @@ var AddonDatabase = { // could be out of order due to schema changes. insertScreenshot: "INSERT INTO screenshot (addon_internal_id, " + "num, url, width, height, thumbnailURL, " + "thumbnailWidth, thumbnailHeight, caption) " + "VALUES (:addon_internal_id, " + ":num, :url, :width, :height, :thumbnailURL, " + ":thumbnailWidth, :thumbnailHeight, :caption)", + insertCompatibilityOverride: "INSERT INTO compatibility_override VALUES " + + "(:addon_internal_id, :num, :type, " + + ":minVersion, :maxVersion, :appID, " + + ":appMinVersion, :appMaxVersion)", + emptyAddon: "DELETE FROM addon" }, /** * A helper function to log an SQL error. * * @param aError * The storage error code associated with the error @@ -1379,39 +1556,52 @@ var AddonDatabase = { } return tryAgain(); } this.connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); if (dbMissing) this._createSchema(); - switch (this.connection.schemaVersion) { - case 0: - this._createSchema(); - break; - case 1: - try { + try { + switch (this.connection.schemaVersion) { + case 0: + LOG("Recreating database schema"); + this._createSchema(); + break; + case 1: + LOG("Upgrading database schema"); this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN width INTEGER"); this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN height INTEGER"); this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailWidth INTEGER"); this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailHeight INTEGER"); - this._createIndices(); - this.connection.schemaVersion = DB_SCHEMA; - } catch (e) { - ERROR("Failed to create database schema", e); - this.logSQLError(this.connection.lastError, this.connection.lastErrorString); - this.connection.rollbackTransaction(); + case 2: + this.connection.createTable("compatibility_override", + "addon_internal_id INTEGER, " + + "num INTEGER, " + + "type TEXT, " + + "minVersion TEXT, " + + "maxVersion TEXT, " + + "appID TEXT, " + + "appMinVersion TEXT, " + + "appMaxVersion TEXT, " + + "PRIMARY KEY (addon_internal_id, num)"); + this._createIndices(); + this._createTriggers(); + this.connection.schemaVersion = DB_SCHEMA; + case 3: + break; + default: return tryAgain(); - } - break; - case 2: - break; - default: - return tryAgain(); + } + } catch (e) { + ERROR("Failed to create database schema", e); + this.logSQLError(this.connection.lastError, this.connection.lastErrorString); + this.connection.rollbackTransaction(); + return tryAgain(); } return this.connection; }, /** * A lazy getter for the database connection. */ @@ -1590,16 +1780,48 @@ var AddonDatabase = { handleCompletion: function(aReason) { if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) { ERROR("Error retrieving screenshots from database. Returning empty results"); aCallback({}); return; } + getAllCompatOverrides(); + } + }); + } + + function getAllCompatOverrides() { + self.getAsyncStatement("getAllCompatOverrides").executeAsync({ + handleResult: function(aResults) { + let row = null; + while (row = aResults.getNextRow()) { + let addon_internal_id = row.getResultByName("addon_internal_id"); + if (!(addon_internal_id in addons)) { + WARN("Found a compatibility override not linked to an add-on in database"); + continue; + } + + let addon = addons[addon_internal_id]; + if (!addon.compatibilityOverrides) + addon.compatibilityOverrides = []; + addon.compatibilityOverrides.push(self._makeCompatOverrideFromAsyncRow(row)); + } + }, + + handleError: self.asyncErrorLogger, + + handleCompletion: function(aReason) { + if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) { + ERROR("Error retrieving compatibility overrides from database. Returning empty results"); + aCallback({}); + return; + } + let returnedAddons = {}; for each (let addon in addons) returnedAddons[addon.id] = addon; aCallback(returnedAddons); } }); } @@ -1672,18 +1894,19 @@ var AddonDatabase = { * @param aCallback * The callback to call once complete */ _insertAddon: function AD__insertAddon(aAddon, aCallback) { let self = this; let internal_id = null; this.connection.beginTransaction(); - // Simultaneously insert the developers and screenshots of the add-on - function insertDevelopersAndScreenshots() { + // Simultaneously insert the developers, screenshots, and compatibility + // overrides of the add-on. + function insertAdditionalData() { let stmts = []; // Initialize statement and parameters for inserting an array function initializeArrayInsert(aStatementKey, aArray, aAddParams) { if (!aArray || aArray.length == 0) return; let stmt = self.getAsyncStatement(aStatementKey); @@ -1691,35 +1914,39 @@ var AddonDatabase = { aArray.forEach(function(aElement, aIndex) { aAddParams(params, internal_id, aElement, aIndex); }); stmt.bindParameters(params); stmts.push(stmt); } - // Initialize statements to insert developers and screenshots + // Initialize statements to insert developers, screenshots, and + // compatibility overrides initializeArrayInsert("insertDeveloper", aAddon.developers, self._addDeveloperParams); initializeArrayInsert("insertScreenshot", aAddon.screenshots, self._addScreenshotParams); + initializeArrayInsert("insertCompatibilityOverride", + aAddon.compatibilityOverrides, + self._addCompatOverrideParams); // Immediately call callback if nothing to insert if (stmts.length == 0) { self.connection.commitTransaction(); aCallback(); return; } self.connection.executeAsync(stmts, stmts.length, { handleResult: function() {}, handleError: self.asyncErrorLogger, handleCompletion: function(aReason) { if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) { - ERROR("Error inserting developers and screenshots into database. Attempting to continue"); + ERROR("Error inserting additional addon metadata into database. Attempting to continue"); self.connection.rollbackTransaction(); } else { self.connection.commitTransaction(); } aCallback(); } @@ -1735,17 +1962,17 @@ var AddonDatabase = { if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) { ERROR("Error inserting add-ons into database. Attempting to continue."); self.connection.rollbackTransaction(); aCallback(); return; } internal_id = self.connection.lastInsertRowID; - insertDevelopersAndScreenshots(); + insertAdditionalData(); } }); }, /** * Make an asynchronous statement that will insert the specified add-on * * @param aAddon @@ -1822,16 +2049,45 @@ var AddonDatabase = { bp.bindByName("thumbnailURL", aScreenshot.thumbnailURL); bp.bindByName("thumbnailWidth", aScreenshot.thumbnailWidth); bp.bindByName("thumbnailHeight", aScreenshot.thumbnailHeight); bp.bindByName("caption", aScreenshot.caption); aParams.addParams(bp); }, /** + * Add compatibility override parameters to the specified + * mozIStorageBindingParamsArray. + * + * @param aParams + * The mozIStorageBindingParamsArray to add the parameters to + * @param aInternalID + * The internal_id of the add-on that this override is for + * @param aOverride + * The override to make the parameters from + * @param aIndex + * The index of this override + */ + _addCompatOverrideParams: function AD_addCompatOverrideParams(aParams, + aInternalID, + aOverride, + aIndex) { + let bp = aParams.newBindingParams(); + bp.bindByName("addon_internal_id", aInternalID); + bp.bindByName("num", aIndex); + bp.bindByName("type", aOverride.type); + bp.bindByName("minVersion", aOverride.minVersion); + bp.bindByName("maxVersion", aOverride.maxVersion); + bp.bindByName("appID", aOverride.appID); + bp.bindByName("appMinVersion", aOverride.appMinVersion); + bp.bindByName("appMaxVersion", aOverride.appMaxVersion); + aParams.addParams(bp); + }, + + /** * Make add-on from an asynchronous row * Note: This add-on will be lacking both developers and screenshots * * @param aRow * The asynchronous row to use * @return The created add-on */ _makeAddonFromAsyncRow: function AD__makeAddonFromAsyncRow(aRow) { @@ -1890,16 +2146,38 @@ var AddonDatabase = { let thumbnailWidth = aRow.getResultByName("thumbnailWidth"); let thumbnailHeight = aRow.getResultByName("thumbnailHeight"); let caption = aRow.getResultByName("caption"); return new AddonManagerPrivate.AddonScreenshot(url, width, height, thumbnailURL, thumbnailWidth, thumbnailHeight, caption); }, /** + * Make a CompatibilityOverride from an asynchronous row + * + * @param aRow + * The asynchronous row to use + * @return The created CompatibilityOverride + */ + _makeCompatOverrideFromAsyncRow: function AD_makeCompatOverrideFromAsyncRow(aRow) { + let type = aRow.getResultByName("type"); + let minVersion = aRow.getResultByName("minVersion"); + let maxVersion = aRow.getResultByName("maxVersion"); + let appID = aRow.getResultByName("appID"); + let appMinVersion = aRow.getResultByName("appMinVersion"); + let appMaxVersion = aRow.getResultByName("appMaxVersion"); + return new AddonManagerPrivate.AddonCompatibilityOverride(type, + minVersion, + maxVersion, + appID, + appMinVersion, + appMaxVersion); + }, + + /** * Synchronously creates the schema in the database. */ _createSchema: function AD__createSchema() { LOG("Creating database schema"); this.connection.beginTransaction(); // Any errors in here should rollback try { @@ -1945,36 +2223,57 @@ var AddonDatabase = { "width INTEGER, " + "height INTEGER, " + "thumbnailURL TEXT, " + "thumbnailWidth INTEGER, " + "thumbnailHeight INTEGER, " + "caption TEXT, " + "PRIMARY KEY (addon_internal_id, num)"); - this._createIndices(); + this.connection.createTable("compatibility_override", + "addon_internal_id INTEGER, " + + "num INTEGER, " + + "type TEXT, " + + "minVersion TEXT, " + + "maxVersion TEXT, " + + "appID TEXT, " + + "appMinVersion TEXT, " + + "appMaxVersion TEXT, " + + "PRIMARY KEY (addon_internal_id, num)"); - this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " + - "ON addon BEGIN " + - "DELETE FROM developer WHERE addon_internal_id=old.internal_id; " + - "DELETE FROM screenshot WHERE addon_internal_id=old.internal_id; " + - "END"); + this._createIndices(); + this._createTriggers(); this.connection.schemaVersion = DB_SCHEMA; this.connection.commitTransaction(); } catch (e) { ERROR("Failed to create database schema", e); this.logSQLError(this.connection.lastError, this.connection.lastErrorString); this.connection.rollbackTransaction(); throw e; } }, /** + * Synchronously creates the triggers in the database. + */ + _createTriggers: function AD__createTriggers() { + this.connection.executeSimpleSQL("DROP TRIGGER IF EXISTS delete_addon"); + this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " + + "ON addon BEGIN " + + "DELETE FROM developer WHERE addon_internal_id=old.internal_id; " + + "DELETE FROM screenshot WHERE addon_internal_id=old.internal_id; " + + "DELETE FROM compatibility_override WHERE addon_internal_id=old.internal_id; " + + "END"); + }, + + /** * Synchronously creates the indices in the database. */ _createIndices: function AD__createIndices() { this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS developer_idx " + "ON developer (addon_internal_id)"); this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS screenshot_idx " + "ON screenshot (addon_internal_id)"); + this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS compatibility_override_idx " + + "ON compatibility_override (addon_internal_id)"); } };
--- a/toolkit/mozapps/extensions/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/extensions/AddonUpdateChecker.jsm @@ -16,16 +16,17 @@ # # The Initial Developer of the Original Code is # the Mozilla Foundation. # Portions created by the Initial Developer are Copyright (C) 2009 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Dave Townsend <dtownsend@oxymoronical.com> +# Blair McBride <bmcbride@mozilla.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -56,16 +57,17 @@ const PREFIX_ITEM = "urn:mozil const PREFIX_EXTENSION = "urn:mozilla:extension:"; const PREFIX_THEME = "urn:mozilla:theme:"; const TOOLKIT_ID = "toolkit@mozilla.org" const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts"; Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/AddonRepository.jsm"); // shared code for suppressing bad cert dialogs Components.utils.import("resource://gre/modules/CertUtils.jsm"); var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. getService(Ci.nsIRDFService); ["LOG", "WARN", "ERROR"].forEach(function(aName) { this.__defineGetter__(aName, function() { @@ -388,16 +390,17 @@ function parseRDFManifest(aId, aType, aU } let result = { id: aId, version: version, updateURL: getProperty(ds, targetApp, "updateLink"), updateHash: getProperty(ds, targetApp, "updateHash"), updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"), + strictCompatibility: getProperty(ds, targetApp, "strictCompatibility") == "true", targetApplications: [appEntry] }; if (result.updateURL && checkSecurity && result.updateURL.substring(0, 6) != "https:" && (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) { WARN("updateLink " + result.updateURL + " is not secure and is not verified" + " by a strong enough hash (needs to be sha1 or stronger)."); @@ -589,29 +592,49 @@ UpdateParser.prototype = { * Tests if an update matches a version of the application or platform * * @param aUpdate * The available update * @param aAppVersion * The application version to use * @param aPlatformVersion * The platform version to use + * @param aIgnoreMaxVersion + * Ignore maxVersion when testing if an update matches. Optional. + * @param aIgnoreStrictCompat + * Ignore strictCompatibility when testing if an update matches. Optional. + * @param aCompatOverrides + * AddonCompatibilityOverride objects to match against. Optional. * @return true if the update is compatible with the application/platform */ -function matchesVersions(aUpdate, aAppVersion, aPlatformVersion) { +function matchesVersions(aUpdate, aAppVersion, aPlatformVersion, + aIgnoreMaxVersion, aIgnoreStrictCompat, + aCompatOverrides) { + if (aCompatOverrides) { + let override = AddonRepository.findMatchingCompatOverride(aUpdate.version, + aCompatOverrides, + aAppVersion, + aPlatformVersion); + if (override && override.type == "incompatible") + return false; + } + + if (aUpdate.strictCompatibility && !aIgnoreStrictCompat) + aIgnoreMaxVersion = false; + let result = false; for (let i = 0; i < aUpdate.targetApplications.length; i++) { let app = aUpdate.targetApplications[i]; if (app.id == Services.appinfo.ID) { return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) && - (Services.vc.compare(aAppVersion, app.maxVersion) <= 0); + (aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0)); } if (app.id == TOOLKIT_ID) { result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) && - (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0); + (aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0)); } } return result; } var AddonUpdateChecker = { // These must be kept in sync with AddonManager // The update check timed out @@ -635,58 +658,74 @@ var AddonUpdateChecker = { * The version of the add-on to get new compatibility information for * @param aIgnoreCompatibility * An optional parameter to get the first compatibility update that * is compatible with any version of the application or toolkit * @param aAppVersion * The version of the application or null to use the current version * @param aPlatformVersion * The version of the platform or null to use the current version + * @param aIgnoreMaxVersion + * Ignore maxVersion when testing if an update matches. Optional. + * @param aIgnoreStrictCompat + * Ignore strictCompatibility when testing if an update matches. Optional. * @return an update object if one matches or null if not */ getCompatibilityUpdate: function AUC_getCompatibilityUpdate(aUpdates, aVersion, aIgnoreCompatibility, aAppVersion, - aPlatformVersion) { + aPlatformVersion, + aIgnoreMaxVersion, + aIgnoreStrictCompat) { if (!aAppVersion) aAppVersion = Services.appinfo.version; if (!aPlatformVersion) aPlatformVersion = Services.appinfo.platformVersion; for (let i = 0; i < aUpdates.length; i++) { if (Services.vc.compare(aUpdates[i].version, aVersion) == 0) { if (aIgnoreCompatibility) { for (let j = 0; j < aUpdates[i].targetApplications.length; j++) { let id = aUpdates[i].targetApplications[j].id; if (id == Services.appinfo.ID || id == TOOLKIT_ID) return aUpdates[i]; } } - else if (matchesVersions(aUpdates[i], aAppVersion, aPlatformVersion)) { + else if (matchesVersions(aUpdates[i], aAppVersion, aPlatformVersion, + aIgnoreMaxVersion, aIgnoreStrictCompat)) { return aUpdates[i]; } } } return null; }, /** * Returns the newest available update from a list of update objects. * * @param aUpdates * An array of update objects * @param aAppVersion * The version of the application or null to use the current version * @param aPlatformVersion * The version of the platform or null to use the current version + * @param aIgnoreMaxVersion + * When determining compatible updates, ignore maxVersion. Optional. + * @param aIgnoreMaxVersion + * When determining compatible updates, ignore strictCompatibility. Optional. + * @param aCompatOverrides + * Array of AddonCompatibilityOverride to take into account. Optional. * @return an update object if one matches or null if not */ getNewestCompatibleUpdate: function AUC_getNewestCompatibleUpdate(aUpdates, aAppVersion, - aPlatformVersion) { + aPlatformVersion, + aIgnoreMaxVersion, + aIgnoreStrictCompat, + aCompatOverrides) { if (!aAppVersion) aAppVersion = Services.appinfo.version; if (!aPlatformVersion) aPlatformVersion = Services.appinfo.platformVersion; let blocklist = Cc["@mozilla.org/extensions/blocklist;1"]. getService(Ci.nsIBlocklistService); @@ -694,18 +733,21 @@ var AddonUpdateChecker = { for (let i = 0; i < aUpdates.length; i++) { if (!aUpdates[i].updateURL) continue; let state = blocklist.getAddonBlocklistState(aUpdates[i].id, aUpdates[i].version, aAppVersion, aPlatformVersion); if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) continue; if ((newest == null || (Services.vc.compare(newest.version, aUpdates[i].version) < 0)) && - matchesVersions(aUpdates[i], aAppVersion, aPlatformVersion)) + matchesVersions(aUpdates[i], aAppVersion, aPlatformVersion, + aIgnoreMaxVersion, aIgnoreStrictCompat, + aCompatOverrides)) { newest = aUpdates[i]; + } } return newest; }, /** * Starts an update check. * * @param aId
--- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -1169,16 +1169,23 @@ function escapeAddonURI(aAddon, aUri, aU // maxVersion let app = aAddon.matchingTargetApplication; if (app) var maxVersion = app.maxVersion; else maxVersion = ""; uri = uri.replace(/%ITEM_MAXAPPVERSION%/g, maxVersion); + let compatMode = "normal"; + if (!XPIProvider.checkCompatibility) + compatMode = "ignore"; + else if (AddonManager.strictCompatibility) + compatMode = "strict"; + uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode); + // Replace custom parameters (names of custom parameters must have at // least 3 characters to prevent lookups for something like %D0%C8) var catMan = null; uri = uri.replace(/%(\w{3,})%/g, function(aMatch, aParam) { if (!catMan) { catMan = Cc["@mozilla.org/categorymanager;1"]. getService(Ci.nsICategoryManager); } @@ -3342,16 +3349,51 @@ var XPIProvider = { updateAddonAppDisabledStates: function XPI_updateAddonAppDisabledStates() { let addons = XPIDatabase.getAddons(); addons.forEach(function(aAddon) { this.updateAddonDisabledState(aAddon); }, this); }, /** + * Update the repositoryAddon property for all add-ons. + * + * @param aCallback + * Function to call when operation is complete. + */ + updateAddonRepositoryData: function XPI_updateAddonRepositoryData(aCallback) { + let self = this; + XPIDatabase.getVisibleAddons(null, function UARD_getVisibleAddonsCallback(aAddons) { + let pending = aAddons.length; + if (pending == 0) { + aCallback(); + return; + } + + function notifyComplete() { + if (--pending == 0) + aCallback(); + } + + aAddons.forEach(function UARD_forEachCallback(aAddon) { + AddonRepository.getCachedAddonByID(aAddon.id, + function UARD_getCachedAddonCallback(aRepoAddon) { + if (aRepoAddon) { + aAddon._repositoryAddon = aRepoAddon; + aAddon.compatibilityOverrides = aRepoAddon.compatibilityOverrides; + self.updateAddonDisabledState(aAddon); + } + + notifyComplete(); + }); + }); + }); + }, + + /** * When the previously selected theme is removed this method will be called * to enable the default theme. */ enableDefaultTheme: function XPI_enableDefaultTheme() { LOG("Activating default theme"); let addon = XPIDatabase.getVisibleAddonForInternalName(this.defaultSkin); if (addon) { if (addon.userDisabled) { @@ -4047,16 +4089,19 @@ AsyncAddonListCallback.prototype = { handleResult: function(aResults) { let row = null; while (row = aResults.getNextRow()) { this.count++; let self = this; XPIDatabase.makeAddonFromRowAsync(row, function(aAddon) { function completeAddon(aRepositoryAddon) { aAddon._repositoryAddon = aRepositoryAddon; + aAddon.compatibilityOverrides = aRepositoryAddon ? + aRepositoryAddon.compatibilityOverrides : + null; self.addons.push(aAddon); if (self.complete && self.addons.length == self.count) self.callback(self.addons); } if ("getCachedAddonByID" in AddonRepository) AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); else @@ -6147,24 +6192,28 @@ AddonInstall.prototype = { * XPI is incorrectly signed */ loadManifest: function AI_loadManifest(aCallback) { function addRepositoryData(aAddon) { // Try to load from the existing cache first AddonRepository.getCachedAddonByID(aAddon.id, function(aRepoAddon) { if (aRepoAddon) { aAddon._repositoryAddon = aRepoAddon; + aAddon.compatibilityOverrides = aRepoAddon.compatibilityOverrides; aCallback(); return; } // It wasn't there so try to re-download it AddonRepository.cacheAddons([aAddon.id], function() { AddonRepository.getCachedAddonByID(aAddon.id, function(aRepoAddon) { aAddon._repositoryAddon = aRepoAddon; + aAddon.compatibilityOverrides = aRepoAddon ? + aRepoAddon.compatibilityOverrides : + null; aCallback(); }); }); }); } let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"]. createInstance(Ci.nsIZipReader); @@ -6965,34 +7014,50 @@ UpdateChecker.prototype = { * Called when AddonUpdateChecker completes the update check * * @param updates * The list of update details for the add-on */ onUpdateCheckComplete: function UC_onUpdateCheckComplete(aUpdates) { let AUC = AddonUpdateChecker; + let ignoreMaxVersion = false; + let ignoreStrictCompat = false; + if (!XPIProvider.checkCompatibility) { + ignoreMaxVersion = true; + ignoreStrictCompat = true; + } else if (this.addon.type == "extension" && + !AddonManager.strictCompatibility && + !this.addon.strictCompatibility && + !this.addon.hasBinaryComponents) { + ignoreMaxVersion = true; + } + // Always apply any compatibility update for the current version let compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version, - this.syncCompatibility); - + this.syncCompatibility, + null, null, + ignoreMaxVersion, + ignoreStrictCompat); // Apply the compatibility update to the database if (compatUpdate) this.addon.applyCompatibilityUpdate(compatUpdate, this.syncCompatibility); // If the request is for an application or platform version that is // different to the current application or platform version then look for a // compatibility update for those versions. if ((this.appVersion && Services.vc.compare(this.appVersion, Services.appinfo.version) != 0) || (this.platformVersion && Services.vc.compare(this.platformVersion, Services.appinfo.platformVersion) != 0)) { compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version, false, this.appVersion, - this.platformVersion); + this.platformVersion, + ignoreMaxVersion, + ignoreStrictCompat); } if (compatUpdate) this.callListener("onCompatibilityUpdateAvailable", createWrapper(this.addon)); else this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon)); function sendUpdateAvailableMessages(aSelf, aInstall) { @@ -7002,19 +7067,26 @@ UpdateChecker.prototype = { } else { aSelf.callListener("onNoUpdateAvailable", createWrapper(aSelf.addon)); } aSelf.callListener("onUpdateFinished", createWrapper(aSelf.addon), AddonManager.UPDATE_STATUS_NO_ERROR); } + let compatOverrides = AddonManager.strictCompatibility ? + null : + this.addon.compatibilityOverrides; + let update = AUC.getNewestCompatibleUpdate(aUpdates, this.appVersion, - this.platformVersion); + this.platformVersion, + ignoreMaxVersion, + ignoreStrictCompat, + compatOverrides); if (update && Services.vc.compare(this.addon.version, update.version) < 0) { for (let i = 0; i < XPIProvider.installs.length; i++) { // Skip installs that don't match the available update if (XPIProvider.installs[i].existingAddon != this.addon || XPIProvider.installs[i].version != update.version) continue; @@ -7144,16 +7216,27 @@ AddonInternal.prototype = { else if (app.id == TOOLKIT_ID) version = aPlatformVersion // Only extensions can be compatible by default; themes and language packs // always use strict compatibility checking. if (this.type == "extension" && !AddonManager.strictCompatibility && !this.strictCompatibility && !this.hasBinaryComponents) { + // The repository can specify compatibility overrides. + // Note: For now, only blacklisting is supported by overrides. + if (this._repositoryAddon && + this._repositoryAddon.compatibilityOverrides) { + let overrides = this._repositoryAddon.compatibilityOverrides; + let override = AddonRepository.findMatchingCompatOverride(this.version, + overrides); + if (override && override.type == "incompatible") + return false; + } + // Extremely old extensions should not be compatible by default. let minCompatVersion; if (app.id == Services.appinfo.ID) minCompatVersion = XPIProvider.minCompatibleAppVersion; else if (app.id == TOOLKIT_ID) minCompatVersion = XPIProvider.minCompatiblePlatformVersion; if (minCompatVersion && @@ -7247,17 +7330,17 @@ AddonInternal.prototype = { * than that in the install manifest, like compatibility information. * * @param aObj * A JS object containing the cached metadata */ importMetadata: function(aObj) { ["targetApplications", "userDisabled", "softDisabled", "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", "updateDate", - "applyBackgroundUpdates"].forEach(function(aProp) { + "applyBackgroundUpdates", "compatibilityOverrides"].forEach(function(aProp) { if (!(aProp in aObj)) return; this[aProp] = aObj[aProp]; }, this); // Compatibility info may have changed so update appDisabled this.appDisabled = !isUsableAddon(this); @@ -7363,17 +7446,17 @@ function AddonWrapper(aAddon) { } return [objValue, false]; } ["id", "syncGUID", "version", "type", "isCompatible", "isPlatformCompatible", "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled", "softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents", - "strictCompatibility"].forEach(function(aProp) { + "strictCompatibility", "compatibilityOverrides"].forEach(function(aProp) { this.__defineGetter__(aProp, function() aAddon[aProp]); }, this); ["fullDescription", "developerComments", "eula", "supportURL", "contributionURL", "contributionAmount", "averageRating", "reviewCount", "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers", "repositoryStatus"].forEach(function(aProp) { this.__defineGetter__(aProp, function() {
--- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -1720,16 +1720,26 @@ var gHeader = { window.addEventListener("focus", function(aEvent) { if (aEvent.target == window) updateNavButtonVisibility(); }, false); updateNavButtonVisibility(); }, + focusSearchBox: function() { + this._search.focus(); + }, + + onKeyPress: function(aEvent) { + if (String.fromCharCode(aEvent.charCode) == "/") { + this.focusSearchBox(); + } + }, + get shouldShowNavButtons() { var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem); // If there is no outer frame then make the buttons visible if (docshellItem.rootTreeItem == docshellItem) return true;
--- a/toolkit/mozapps/extensions/content/extensions.xul +++ b/toolkit/mozapps/extensions/content/extensions.xul @@ -50,17 +50,18 @@ <page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xhtml="http://www.w3.org/1999/xhtml" id="addons-page" title="&addons.windowTitle;" role="application" windowtype="Addons:Manager" disablefastfind="true" ondragenter="gDragDrop.onDragOver(event)" ondragover="gDragDrop.onDragOver(event)" - ondrop="gDragDrop.onDrop(event)"> + ondrop="gDragDrop.onDrop(event)" + onkeypress="gHeader.onKeyPress(event)"> <xhtml:link rel="shortcut icon" href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/> <script type="application/javascript" src="chrome://mozapps/content/extensions/extensions.js"/> <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/> @@ -140,16 +141,21 @@ <command id="cmd_installItem"/> <command id="cmd_purchaseItem"/> <command id="cmd_uninstallItem"/> <command id="cmd_cancelUninstallItem"/> <command id="cmd_cancelOperation"/> <command id="cmd_contribute"/> </commandset> + <keyset> + <key id="focusSearch" key="&search.commandkey;" modifiers="accel" + oncommand="gHeader.focusSearchBox();"/> + </keyset> + <!-- main header --> <hbox id="header" align="center"> <toolbarbutton id="back-btn" class="nav-button header-button" command="cmd_back" tooltiptext="&cmd.back.tooltip;" hidden="true" disabled="true"/> <toolbarbutton id="forward-btn" class="nav-button header-button" command="cmd_forward" tooltiptext="&cmd.forward.tooltip;" hidden="true" disabled="true"/> <spacer flex="1"/> <hbox id="updates-container" align="center">
--- a/toolkit/mozapps/extensions/test/browser/Makefile.in +++ b/toolkit/mozapps/extensions/test/browser/Makefile.in @@ -52,16 +52,17 @@ include $(DEPTH)/config/autoconf.mk browser_bug557943.js \ browser_bug562797.js \ browser_bug562854.js \ browser_bug562890.js \ browser_bug562899.js \ browser_bug562992.js \ browser_bug567127.js \ browser_bug567137.js \ + browser_bug570760.js \ browser_bug572561.js \ browser_bug577990.js \ browser_bug580298.js \ browser_bug581076.js \ browser_bug586574.js \ browser_bug587970.js \ browser_bug591465.js \ browser_bug591663.js \
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/browser_bug570760.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Bug 570760 - Make ctrl-f and / focus the search box in the add-ons manager + +var gManagerWindow; +var focusCount = 0; + +function test() { + waitForExplicitFinish(); + + open_manager(null, function(aWindow) { + gManagerWindow = aWindow; + + var searchBox = gManagerWindow.document.getElementById("header-search"); + function focusHandler() { + searchBox.blur(); + focusCount++; + } + searchBox.addEventListener("focus", focusHandler); + f_key_test(); + slash_key_test(); + searchBox.removeEventListener("focus", focusHandler); + end_test(); + }); +} + +function end_test() { + close_manager(gManagerWindow, finish); +} + +function f_key_test() { + EventUtils.synthesizeKey("f", { accelKey: true }, gManagerWindow); + is(focusCount, 1, "Search box should have been focused due to the f key"); +} + +function slash_key_test() { + EventUtils.synthesizeKey("/", { }, gManagerWindow); + is(focusCount, 2, "Search box should have been focused due to the / key"); +}
--- a/toolkit/mozapps/extensions/test/browser/browser_searching.js +++ b/toolkit/mozapps/extensions/test/browser/browser_searching.js @@ -34,16 +34,17 @@ else { } const COMPATIBILITY_PREF = "extensions.checkCompatibility." + version; function test() { requestLongerTimeout(2); // Turn on searching for this test Services.prefs.setIntPref(PREF_SEARCH_MAXRESULTS, 15); + Services.prefs.setBoolPref(PREF_STRICT_COMPAT, true); waitForExplicitFinish(); gProvider = new MockProvider(); gProvider.createAddons([{ id: "addon1@tests.mozilla.org", name: "PASS - f", @@ -616,16 +617,38 @@ add_test(function() { var item = get_addon_item("remote6"); is(item, null, "Addon incompatible with the product should not be visible"); Services.prefs.clearUserPref(COMPATIBILITY_PREF); run_next_test(); }); }); +// Tests that compatible-by-default addons are shown if strict compatibility checking is disabled +add_test(function() { + restart_manager(gManagerWindow, null, function(aWindow) { + gManagerWindow = aWindow; + gCategoryUtilities = new CategoryUtilities(gManagerWindow); + + Services.prefs.setBoolPref(PREF_STRICT_COMPAT, false); + search("incompatible", false, function() { + var item = get_addon_item("remote5"); + is_element_visible(item, "Incompatible addon should be visible"); + isnot(item.getAttribute("notification"), "warning", "Compatibility warning should not be shown"); + + var item = get_addon_item("remote6"); + is(item, null, "Addon incompatible with the product should not be visible"); + + Services.prefs.setBoolPref(PREF_STRICT_COMPAT, true); + run_next_test(); + }); + }); +}); + + // Tests that restarting the manager doesn't change search results add_test(function() { restart_manager(gManagerWindow, null, function(aWindow) { gManagerWindow = aWindow; gCategoryUtilities = new CategoryUtilities(gManagerWindow); // We never restore to the search pane is(gCategoryUtilities.selectedCategory, "discover", "View should have changed to discover");
--- a/toolkit/mozapps/extensions/test/browser/browser_select_update.js +++ b/toolkit/mozapps/extensions/test/browser/browser_select_update.js @@ -117,18 +117,16 @@ function setupUI(aFailDownloads, aFailIn }); EventUtils.synthesizeMouseAtCenter(gWin.document.getElementById("next"), {}, gWin); }); }, gWin); } function test() { waitForExplicitFinish(); - requestLongerTimeout(100); - run_next_test(); } function end_test() { finish(); } // Test for working updates
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.xml +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.xml @@ -110,10 +110,73 @@ </preview> <preview primary="0"> <full type="image/png">http://localhost:4444/repo/3/secondFull.png</full> <thumbnail type="image/png">http://localhost:4444/repo/3/secondThumbnail.png</thumbnail> <caption>Repo Add-on 3 - Second Caption</caption> </preview> </previews> </addon> + + <addon_compatibility hosted="true" id="123"> + <guid>test_AddonRepository_1@tests.mozilla.org</guid> + <name>PASS</name> + <version_ranges> + <!-- Will be included --> + <version_range type="incompatible"> + <min_version>0.1</min_version> + <max_version>0.2</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <application_id>666</application_id> + <min_version>3.0</min_version> + <max_version>4.0</max_version> + <appID>xpcshell@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + <!-- Will be included --> + <version_range type="incompatible"> + <min_version>0.2</min_version> + <max_version>0.3</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <application_id>666</application_id> + <min_version>5.0</min_version> + <max_version>6.0</max_version> + <appID>xpcshell@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + <!-- Won't be included - invalid type attribute --> + <version_range type="unknown"> + <min_version>9</min_version> + <max_version>10</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <application_id>666</application_id> + <min_version>10.0</min_version> + <max_version>11.0</max_version> + <appID>xpcshell@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + <!-- Won't be included - no matching appID --> + <version_range type="incompatible"> + <min_version>0.2</min_version> + <max_version>0.3</max_version> + <compatible_applications> + <application> + <name>Unknown App</name> + <application_id>123</application_id> + <min_version>1.0</min_version> + <max_version>999.0</max_version> + <appID>unknown-app@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> </searchresults>
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.xml +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.xml @@ -57,16 +57,79 @@ <reviews num="1111">http://localhost:4444/review1.html</reviews> <total_downloads>2222</total_downloads> <weekly_downloads>3333</weekly_downloads> <daily_users>4444</daily_users> <last_updated epoch="1265033045">2010-02-01T14:04:05Z</last_updated> <install size="5555">http://localhost:4444/addons/test_AddonRepository_2.xpi</install> </addon> + <addon_compatibility hosted="true" id="123"> + <guid>test1@tests.mozilla.org</guid> + <name>PASS</name> + <version_ranges> + <!-- Will be included --> + <version_range type="incompatible"> + <min_version>0.1</min_version> + <max_version>0.2</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <application_id>666</application_id> + <min_version>3.0</min_version> + <max_version>4.0</max_version> + <appID>xpcshell@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + <!-- Will be included --> + <version_range type="incompatible"> + <min_version>0.2</min_version> + <max_version>0.3</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <application_id>666</application_id> + <min_version>5.0</min_version> + <max_version>6.0</max_version> + <appID>xpcshell@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + <!-- Won't be included - invalid type attribute --> + <version_range type="unknown"> + <min_version>9</min_version> + <max_version>10</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <application_id>666</application_id> + <min_version>10.0</min_version> + <max_version>11.0</max_version> + <appID>xpcshell@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + <!-- Won't be included - no matching appID --> + <version_range type="incompatible"> + <min_version>0.2</min_version> + <max_version>0.3</max_version> + <compatible_applications> + <application> + <name>Unknown App</name> + <application_id>123</application_id> + <min_version>1.0</min_version> + <max_version>999.0</max_version> + <appID>unknown-app@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + <!-- Fails because guid matches previously successful result --> <addon> <name>FAIL</name> <type id="1">Extension</type> <guid>test1@tests.mozilla.org</guid> <version>1.2</version> <authors><author><name>Test Creator 2</name></author></authors> <status id="4">Public</status>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_backgroundupdate.rdf @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <RDF:Description about="urn:mozilla:extension:addon1@tests.mozilla.org"> + <em:updates> + <RDF:Seq> + <!-- app id compatible update available --> + <RDF:li> + <RDF:Description> + <em:version>2</em:version> + <em:targetApplication> + <RDF:Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>1</em:maxVersion> + <em:updateLink>http://localhost:4444/broken.xpi</em:updateLink> + </RDF:Description> + </em:targetApplication> + </RDF:Description> + </RDF:li> + </RDF:Seq> + </em:updates> + </RDF:Description> + + <RDF:Description about="urn:mozilla:extension:addon2@tests.mozilla.org"> + <em:updates> + <RDF:Seq> + <!-- app id compatible update available --> + <RDF:li> + <RDF:Description> + <em:version>2</em:version> + <em:targetApplication> + <RDF:Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>1</em:maxVersion> + <em:updateLink>http://localhost:4444/broken.xpi</em:updateLink> + </RDF:Description> + </em:targetApplication> + </RDF:Description> + </RDF:li> + </RDF:Seq> + </em:updates> + </RDF:Description> + +</RDF:RDF> +
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_compatoverrides.xml @@ -0,0 +1,228 @@ +<?xml version="1.0" encoding="utf-8" ?> +<searchresults total_results="9"> + <addon> + <name>Test addon 2</name> + <type id="1">Extension</type> + <guid>addon2@tests.mozilla.org</guid> + <version>1.0</version> + </addon> + + <addon> + <name>Test addon 3</name> + <type id="1">Extension</type> + <guid>addon3@tests.mozilla.org</guid> + <version>1.0</version> + </addon> + <addon_compatibility hosted="true"> + <name>Test addon 3</name> + <guid>addon3@tests.mozilla.org</guid> + <version_ranges> + <version_range type="incompatible"> + <min_version>0.9</min_version> + <max_version>1.0</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>1</min_version> + <max_version>2</max_version> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + + <addon> + <name>Test addon 4</name> + <type id="1">Extension</type> + <guid>addon4@tests.mozilla.org</guid> + <version>1.0</version> + </addon> + <addon_compatibility hosted="true"> + <name>Test addon 4</name> + <guid>addon4@tests.mozilla.org</guid> + <version_ranges> + <version_range type="incompatible"> + <min_version>0.9</min_version> + <max_version>1.0</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>1</min_version> + <max_version>2</max_version> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + + <addon> + <name>Test addon 5</name> + <type id="1">Extension</type> + <guid>addon5@tests.mozilla.org</guid> + <version>1.0</version> + </addon> + <addon_compatibility hosted="true"> + <name>Test addon 5</name> + <guid>addon5@tests.mozilla.org</guid> + <version_ranges> + <version_range type="incompatible"> + <min_version>0.9</min_version> + <max_version>1.0</max_version> + <compatible_applications> + <application> + <name>Unknown App</name> + <appID>unknown-app@tests.mozilla.org</appID> + <min_version>1</min_version> + <max_version>2</max_version> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + + <addon> + <name>Test addon 6</name> + <type id="1">Extension</type> + <guid>addon6@tests.mozilla.org</guid> + <version>1.0</version> + </addon> + <addon_compatibility hosted="true"> + <name>Test addon 6</name> + <guid>addon6@tests.mozilla.org</guid> + <version_ranges> + <version_range type="incompatible"> + <min_version>0.5</min_version> + <max_version>0.9</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>1</min_version> + <max_version>2</max_version> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + + <addon> + <name>Test addon 7</name> + <type id="1">Extension</type> + <guid>addon7@tests.mozilla.org</guid> + <version>1.0</version> + </addon> + <addon_compatibility hosted="true"> + <name>Test addon 7</name> + <guid>addon7@tests.mozilla.org</guid> + <version_ranges> + <version_range type="incompatible"> + <min_version>0.5</min_version> + <max_version>1.0</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>0.1</min_version> + <max_version>0.9</max_version> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + + <addon> + <name>Test addon 8</name> + <type id="1">Extension</type> + <guid>addon8@tests.mozilla.org</guid> + <version>1.0</version> + </addon> + <addon_compatibility hosted="true"> + <name>Test addon 8</name> + <guid>addon8@tests.mozilla.org</guid> + <version_ranges> + <version_range type="incompatible"> + <min_version>6</min_version> + <max_version>6.2</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>0.9</min_version> + <max_version>9</max_version> + </application> + </compatible_applications> + </version_range> + <version_range type="incompatible"> + <min_version>0.5</min_version> + <max_version>1.0</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>0.1</min_version> + <max_version>9</max_version> + </application> + <application> + <name>Unknown app</name> + <appID>unknown-app@tests.mozilla.org</appID> + <min_version>0.1</min_version> + <max_version>9</max_version> + </application> + </compatible_applications> + </version_range> + <version_range type="incompatible"> + <min_version>0.1</min_version> + <max_version>0.2</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>0.1</min_version> + <max_version>0.9</max_version> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + + <addon_compatibility hosted="false"> + <name>Test addon 9</name> + <guid>addon9@tests.mozilla.org</guid> + <version_ranges> + <version_range type="incompatible"> + <min_version>0.5</min_version> + <max_version>1.0</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>1</min_version> + <max_version>2</max_version> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + + <addon_compatibility hosted="false"> + <name>Test addon 10</name> + <guid>addon10@tests.mozilla.org</guid> + <version_ranges> + <version_range type="compatible"> + <min_version>0.5</min_version> + <max_version>1.0</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <appID>xpcshell@tests.mozilla.org</appID> + <min_version>1</min_version> + <max_version>2</max_version> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> + +</searchresults>
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.rdf +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.rdf @@ -139,9 +139,112 @@ <em:updateLink>http://localhost:4444/addons/test_update8.xpi</em:updateLink> </Description> </em:targetApplication> </Description> </li> </Seq> </em:updates> </Description> + + <Description about="urn:mozilla:extension:addon9@tests.mozilla.org"> + <em:updates> + <Seq> + <li> + <Description> + <em:version>2.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>1</em:maxVersion> + <em:updateLink>http://localhost:4444/addons/test_update9_2.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + <!-- Incompatible when strict compatibility is enabled --> + <li> + <Description> + <em:version>3.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.9</em:minVersion> + <em:maxVersion>0.9</em:maxVersion> + <em:updateLink>http://localhost:4444/addons/test_update9_3.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + <!-- Incompatible due to compatibility override --> + <li> + <Description> + <em:version>4.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.9</em:minVersion> + <em:maxVersion>0.9</em:maxVersion> + <em:updateLink>http://localhost:4444/addons/test_update9_4.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + <!-- Addon for future version of app --> + <li> + <Description> + <em:version>5.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>5</em:minVersion> + <em:maxVersion>6</em:maxVersion> + <em:updateLink>http://localhost:4444/addons/test_update9_5.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> + + <Description about="urn:mozilla:extension:addon10@tests.mozilla.org"> + <em:updates> + <Seq> + <li> + <Description> + <em:version>1.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.1</em:minVersion> + <em:maxVersion>0.4</em:maxVersion> + <em:updateLink>http://localhost:4444/addons/test_update10.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> + + <Description about="urn:mozilla:extension:addon11@tests.mozilla.org"> + <em:updates> + <Seq> + <li> + <Description> + <em:version>2.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.1</em:minVersion> + <em:maxVersion>0.2</em:maxVersion> + <em:strictCompatibility>true</em:strictCompatibility> + <em:updateLink>http://localhost:4444/addons/test_update11.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> </RDF>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" ?> +<searchresults total_results="11"> + <addon> + <name>Test Addon 9</name> + <type id="1">Extension</type> + <guid>addon9@tests.mozilla.org</guid> + </addon> + <addon_compatibility hosted="true"> + <guid>addon9@tests.mozilla.org</guid> + <name>Test Addon 9</name> + <version_ranges> + <version_range type="incompatible"> + <min_version>4</min_version> + <max_version>4</max_version> + <compatible_applications> + <application> + <name>XPCShell</name> + <min_version>1</min_version> + <max_version>1</max_version> + <appID>xpcshell@tests.mozilla.org</appID> + </application> + </compatible_applications> + </version_range> + </version_ranges> + </addon_compatibility> +</searchresults>
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf @@ -291,9 +291,129 @@ </RDF:Description> <!-- There should be no information present for test_bug378216_14 --> <!-- Invalid update RDF --> <RDF:Description about="urn:mozilla:extension:test_bug378216_15@tests.mozilla.org"> <em:updates>Foo</em:updates> </RDF:Description> + + <!-- Various updates available - one is not compatible, but compatibility checking is disabled --> + <Description about="urn:mozilla:extension:ignore-compat@tests.mozilla.org"> + <em:updates> + <Seq> + <li> + <Description> + <em:version>1.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.1</em:minVersion> + <em:maxVersion>0.2</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test1.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + <li> + <Description> + <em:version>2.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.5</em:minVersion> + <em:maxVersion>0.6</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test2.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + <!-- Update for future app versions - should never be compatible --> + <li> + <Description> + <em:version>3.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>2</em:minVersion> + <em:maxVersion>3</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test3.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> + + <!-- Various updates available - one is not compatible, but compatibility checking is disabled --> + <Description about="urn:mozilla:extension:compat-override@tests.mozilla.org"> + <em:updates> + <Seq> + <!-- Has compatibility override, but it doesn't match this app version --> + <li> + <Description> + <em:version>1.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.1</em:minVersion> + <em:maxVersion>0.2</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test1.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + <!-- Has compatibility override, so is incompaible --> + <li> + <Description> + <em:version>2.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.5</em:minVersion> + <em:maxVersion>0.6</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test2.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + <!-- Update for future app versions - should never be compatible --> + <li> + <Description> + <em:version>3.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>2</em:minVersion> + <em:maxVersion>3</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test3.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> + + <!-- Opt-in to strict compatibility checking --> + <Description about="urn:mozilla:extension:compat-strict-optin@tests.mozilla.org"> + <em:updates> + <Seq> + <li> + <Description> + <em:version>1.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>0.1</em:minVersion> + <em:maxVersion>0.2</em:maxVersion> + <em:strictCompatibility>true</em:strictCompatibility> + <em:updateLink>https://localhost:4444/addons/test1.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> </RDF>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_ignore.rdf @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:extension:compatmode-ignore@tests.mozilla.org"> + <em:updates> + <Seq> + <li> + <Description> + <em:version>2.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>2</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test1.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> +</RDF>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_normal.rdf @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:extension:compatmode-normal@tests.mozilla.org"> + <em:updates> + <Seq> + <li> + <Description> + <em:version>2.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>2</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test1.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> +</RDF>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_strict.rdf @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:extension:compatmode-strict@tests.mozilla.org"> + <em:updates> + <Seq> + <li> + <Description> + <em:version>2.0</em:version> + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>2</em:maxVersion> + <em:updateLink>https://localhost:4444/addons/test1.xpi</em:updateLink> + </Description> + </em:targetApplication> + </Description> + </li> + </Seq> + </em:updates> + </Description> +</RDF>
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -239,16 +239,22 @@ function do_check_addon(aActualAddon, aE case "sourceURI": do_check_eq(actualValue.spec, expectedValue); break; case "updateDate": do_check_eq(actualValue.getTime(), expectedValue.getTime()); break; + case "compatibilityOverrides": + do_check_eq(actualValue.length, expectedValue.length); + for (let i = 0; i < actualValue.length; i++) + do_check_compatibilityoverride(actualValue[i], expectedValue[i]); + break; + default: if (actualValue !== expectedValue) do_throw("Failed for " + aProperty + " for add-on " + aExpectedAddon.id + " (" + actualValue + " === " + expectedValue + ")"); } }); } @@ -281,16 +287,34 @@ function do_check_screenshot(aActual, aE do_check_eq(aActual.height, aExpected.height); do_check_eq(aActual.thumbnailURL, aExpected.thumbnailURL); do_check_eq(aActual.thumbnailWidth, aExpected.thumbnailWidth); do_check_eq(aActual.thumbnailHeight, aExpected.thumbnailHeight); do_check_eq(aActual.caption, aExpected.caption); } /** + * Check that the actual compatibility override is the same as the expected + * compatibility override. + * + * @param aAction + * The actual compatibility override to check. + * @param aExpected + * The expected compatibility override to check against. + */ +function do_check_compatibilityoverride(aActual, aExpected) { + do_check_eq(aActual.type, aExpected.type); + do_check_eq(aActual.minVersion, aExpected.minVersion); + do_check_eq(aActual.maxVersion, aExpected.maxVersion); + do_check_eq(aActual.appID, aExpected.appID); + do_check_eq(aActual.appMinVersion, aExpected.appMinVersion); + do_check_eq(aActual.appMaxVersion, aExpected.appMaxVersion); +} + +/** * Starts up the add-on manager as if it was started by the application. * * @param aAppChanged * An optional boolean parameter to simulate the case where the * application has changed version since the last run. If not passed it * defaults to true */ function startupManager(aAppChanged) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js @@ -31,17 +31,18 @@ const INSTALL_URL3 = "/addons/test_Addo // Note: name is checked separately var ADDON_PROPERTIES = ["id", "type", "version", "creator", "developers", "description", "fullDescription", "developerComments", "eula", "iconURL", "screenshots", "homepageURL", "supportURL", "contributionURL", "contributionAmount", "averageRating", "reviewCount", "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers", "sourceURI", "repositoryStatus", "size", "updateDate", - "purchaseURL", "purchaseAmount", "purchaseDisplayAmount"]; + "purchaseURL", "purchaseAmount", "purchaseDisplayAmount", + "compatibilityOverrides"]; // Results of getAddonsByIDs var GET_RESULTS = [{ id: "test1@tests.mozilla.org", type: "extension", version: "1.1", creator: { name: "Test Creator 1", @@ -77,17 +78,32 @@ var GET_RESULTS = [{ reviewCount: 1111, reviewURL: BASE_URL + "/review1.html", totalDownloads: 2222, weeklyDownloads: 3333, dailyUsers: 4444, sourceURI: BASE_URL + INSTALL_URL2, repositoryStatus: 8, size: 5555, - updateDate: new Date(1265033045000) + updateDate: new Date(1265033045000), + compatibilityOverrides: [{ + type: "incompatible", + minVersion: 0.1, + maxVersion: 0.2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 3.0, + appMaxVersion: 4.0 + }, { + type: "incompatible", + minVersion: 0.2, + maxVersion: 0.3, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 5.0, + appMaxVersion: 6.0 + }] }, { id: "test_AddonRepository_1@tests.mozilla.org", version: "1.4", repositoryStatus: 9999 }]; // Results of retrieveRecommendedAddons and searchAddons var SEARCH_RESULTS = [{ @@ -171,17 +187,18 @@ var SEARCH_RESULTS = [{ reviewCount: 1111, reviewURL: BASE_URL + "/review3.html", totalDownloads: 2222, weeklyDownloads: 3333, dailyUsers: 4444, sourceURI: BASE_URL + "/test3.xpi", repositoryStatus: 8, size: 5555, - updateDate: new Date(1265033045000) + updateDate: new Date(1265033045000), + }, { id: "purchase1@tests.mozilla.org", type: "extension", version: "2.0", creator: { name: "Test Creator - Last Passing", url: BASE_URL + "/creatorLastPassing.html" },
--- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -34,17 +34,18 @@ const PREF_ADDON1_CACHE_ENABLED = "exten const ADDON_PROPERTIES = ["id", "type", "name", "version", "creator", "developers", "translators", "contributors", "description", "fullDescription", "developerComments", "eula", "iconURL", "screenshots", "homepageURL", "supportURL", "optionsURL", "aboutURL", "contributionURL", "contributionAmount", "averageRating", "reviewCount", "reviewURL", "totalDownloads", "weeklyDownloads", - "dailyUsers", "sourceURI", "repositoryStatus"]; + "dailyUsers", "sourceURI", "repositoryStatus", + "compatibilityOverrides"]; // The size and updateDate properties are annoying to test for XPI add-ons. // However, since we only care about whether the repository value vs. the // XPI value is used, we can just test if the property value matches // the repository value const REPOSITORY_SIZE = 9; const REPOSITORY_UPDATEDATE = 9; @@ -85,17 +86,32 @@ const REPOSITORY_ADDONS = [{ contributionAmount: "$11.11", averageRating: 1, reviewCount: 1111, reviewURL: BASE_URL + "/repo/1/review.html", totalDownloads: 2221, weeklyDownloads: 3331, dailyUsers: 4441, sourceURI: BASE_URL + "/repo/1/install.xpi", - repositoryStatus: 4 + repositoryStatus: 4, + compatibilityOverrides: [{ + type: "incompatible", + minVersion: 0.1, + maxVersion: 0.2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 3.0, + appMaxVersion: 4.0 + }, { + type: "incompatible", + minVersion: 0.2, + maxVersion: 0.3, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 5.0, + appMaxVersion: 6.0 + }] }, { id: ADDON_IDS[1], type: "theme", name: "Repo Add-on 2", version: "2.2", creator: { name: "Repo Add-on 2 - Creator", url: BASE_URL + "/repo/2/creator.html" @@ -217,17 +233,32 @@ const WITH_CACHE = [{ contributionAmount: "$11.11", averageRating: 1, reviewCount: 1111, reviewURL: BASE_URL + "/repo/1/review.html", totalDownloads: 2221, weeklyDownloads: 3331, dailyUsers: 4441, sourceURI: NetUtil.newURI(ADDON_FILES[0]).spec, - repositoryStatus: 4 + repositoryStatus: 4, + compatibilityOverrides: [{ + type: "incompatible", + minVersion: 0.1, + maxVersion: 0.2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 3.0, + appMaxVersion: 4.0 + }, { + type: "incompatible", + minVersion: 0.2, + maxVersion: 0.3, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 5.0, + appMaxVersion: 6.0 + }] }, { id: ADDON_IDS[1], type: "theme", name: "XPI Add-on 2", version: "1.2", creator: { name: "Repo Add-on 2 - Creator", url: BASE_URL + "/repo/2/creator.html" @@ -314,17 +345,32 @@ const WITH_EXTENSION_CACHE = [{ contributionAmount: "$11.11", averageRating: 1, reviewCount: 1111, reviewURL: BASE_URL + "/repo/1/review.html", totalDownloads: 2221, weeklyDownloads: 3331, dailyUsers: 4441, sourceURI: NetUtil.newURI(ADDON_FILES[0]).spec, - repositoryStatus: 4 + repositoryStatus: 4, + compatibilityOverrides: [{ + type: "incompatible", + minVersion: 0.1, + maxVersion: 0.2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 3.0, + appMaxVersion: 4.0 + }, { + type: "incompatible", + minVersion: 0.2, + maxVersion: 0.3, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 5.0, + appMaxVersion: 6.0 + }] }, { id: ADDON_IDS[1], type: "theme", name: "XPI Add-on 2", version: "1.2", sourceURI: NetUtil.newURI(ADDON_FILES[1]).spec }, { id: ADDON_IDS[2],
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js @@ -0,0 +1,105 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that background update notifications work as expected + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +do_load_httpd_js(); +var testserver; +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + // Create and configure the HTTP server. + testserver = new nsHttpServer(); + testserver.registerDirectory("/data/", do_get_file("data")); + testserver.registerDirectory("/addons/", do_get_file("addons")); + testserver.start(4444); + + startupManager(); + + do_test_pending(); + run_test_1(); +} + +function end_test() { + testserver.stop(do_test_finished); +} + +// Verify that with no add-ons installed the background update notifications get +// called +function run_test_1() { + AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function(aAddons) { + do_check_eq(aAddons.length, 0); + + Services.obs.addObserver(function() { + Services.obs.removeObserver(arguments.callee, "addons-background-update-complete"); + + run_test_2(); + }, "addons-background-update-complete", false); + + AddonManagerPrivate.backgroundUpdateCheck(); + }); +} + +// Verify that with two add-ons installed both of which claim to have updates +// available we get the notification after both updates attempted to start +function run_test_2() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_backgroundupdate.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_backgroundupdate.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 2", + }, profileDir); + + restartManager(); + + let installCount = 0; + let completeCount = 0; + let sawCompleteNotification = false; + + Services.obs.addObserver(function() { + Services.obs.removeObserver(arguments.callee, "addons-background-update-complete"); + + do_check_eq(installCount, 2); + sawCompleteNotification = true; + }, "addons-background-update-complete", false); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + installCount++; + }, + + onDownloadFailed: function(aInstall) { + completeCount++; + if (completeCount == 2) { + do_check_true(sawCompleteNotification); + end_test(); + } + } + }); + + AddonManagerPrivate.backgroundUpdateCheck(); +}
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js @@ -0,0 +1,275 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests compatibility overrides, for when strict compatibility checking is +// disabled. See bug 693906. + + +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; +const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; + +const PORT = 4444; +const BASE_URL = "http://localhost:" + PORT; +const DEFAULT_URL = "about:blank"; +const REQ_URL = "/data.xml"; + +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); +Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); +Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + BASE_URL + REQ_URL); + +do_load_httpd_js(); +var gServer; + + +// Not hosted, no overrides +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test addon 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +// Hosted, no overrides +var addon2 = { + id: "addon2@tests.mozilla.org", + version: "1.0", + name: "Test addon 2", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +// Hosted, matching override +var addon3 = { + id: "addon3@tests.mozilla.org", + version: "1.0", + name: "Test addon 3", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +// Hosted, matching override, wouldn't be compatible if strict chekcing is enabled +var addon4 = { + id: "addon4@tests.mozilla.org", + version: "1.0", + name: "Test addon 4", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }] +}; + +// Hosted, app ID doesn't match in override +var addon5 = { + id: "addon5@tests.mozilla.org", + version: "1.0", + name: "Test addon 5", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +// Hosted, addon version range doesn't match in override +var addon6 = { + id: "addon6@tests.mozilla.org", + version: "1.0", + name: "Test addon 6", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +// Hosted, app version range doesn't match in override +var addon7 = { + id: "addon7@tests.mozilla.org", + version: "1.0", + name: "Test addon 7", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +// Hosted, multiple overrides +var addon8 = { + id: "addon8@tests.mozilla.org", + version: "1.0", + name: "Test addon 8", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +// Not hosted, matching override +var addon9 = { + id: "addon9@tests.mozilla.org", + version: "1.0", + name: "Test addon 9", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +// Not hosted, override is of unsupported type (compatible) +var addon10 = { + id: "addon10@tests.mozilla.org", + version: "1.0", + name: "Test addon 10", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + + +/* + * Trigger an AddonManager background update check + * + * @param aCallback + * Callback to call once the background update is complete + */ +function trigger_background_update(aCallback) { + Services.obs.addObserver({ + observe: function(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, "addons-background-update-complete"); + aCallback(); + } + }, "addons-background-update-complete", false); + + gInternalManager.notify(null); +} + +function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "2"); + + writeInstallRDFForExtension(addon1, profileDir); + writeInstallRDFForExtension(addon2, profileDir); + writeInstallRDFForExtension(addon3, profileDir); + writeInstallRDFForExtension(addon4, profileDir); + writeInstallRDFForExtension(addon5, profileDir); + writeInstallRDFForExtension(addon6, profileDir); + writeInstallRDFForExtension(addon7, profileDir); + writeInstallRDFForExtension(addon8, profileDir); + writeInstallRDFForExtension(addon9, profileDir); + writeInstallRDFForExtension(addon10, profileDir); + + gServer = new nsHttpServer(); + gServer.registerFile(REQ_URL, do_get_file("data/test_compatoverrides.xml")); + gServer.start(PORT); + + startupManager(); + + trigger_background_update(run_test_1); +} + +function end_test() { + gServer.stop(do_test_finished); +} + +function check_compat_status(aCallback) { + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "addon8@tests.mozilla.org", + "addon9@tests.mozilla.org", + "addon10@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]) { + + do_check_neq(a1, null); + do_check_eq(a1.compatibilityOverrides, null); + do_check_true(a1.isCompatible); + do_check_false(a1.appDisabled); + + do_check_neq(a2, null); + do_check_eq(a2.compatibilityOverrides, null); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + + do_check_neq(a3, null); + do_check_neq(a3.compatibilityOverrides, null); + do_check_eq(a3.compatibilityOverrides.length, 1); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + do_check_neq(a4, null); + do_check_neq(a4.compatibilityOverrides, null); + do_check_eq(a4.compatibilityOverrides.length, 1); + do_check_false(a4.isCompatible); + do_check_true(a4.appDisabled); + + do_check_neq(a5, null); + do_check_eq(a5.compatibilityOverrides, null); + do_check_true(a5.isCompatible); + do_check_false(a5.appDisabled); + + do_check_neq(a6, null); + do_check_neq(a6.compatibilityOverrides, null); + do_check_eq(a6.compatibilityOverrides.length, 1); + do_check_true(a6.isCompatible); + do_check_false(a6.appDisabled); + + do_check_neq(a7, null); + do_check_neq(a7.compatibilityOverrides, null); + do_check_eq(a7.compatibilityOverrides.length, 1); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + do_check_neq(a8, null); + do_check_neq(a8.compatibilityOverrides, null); + do_check_eq(a8.compatibilityOverrides.length, 3); + do_check_false(a8.isCompatible); + do_check_true(a8.appDisabled); + + do_check_neq(a9, null); + do_check_neq(a9.compatibilityOverrides, null); + do_check_eq(a9.compatibilityOverrides.length, 1); + do_check_false(a9.isCompatible); + do_check_true(a9.appDisabled); + + do_check_neq(a10, null); + do_check_eq(a10.compatibilityOverrides, null); + do_check_true(a10.isCompatible); + do_check_false(a10.appDisabled); + + aCallback(); + }); +} + +function run_test_1() { + check_compat_status(run_test_2); +} + +function run_test_2() { + restartManager(); + check_compat_status(end_test); +}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js @@ -121,18 +121,20 @@ function prepare_profile() { AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org", "addon5@tests.mozilla.org", "addon6@tests.mozilla.org", "addon9@tests.mozilla.org"], function([a1, a2, a3, a4, a5, a6, a9]) { + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; a2.userDisabled = true; - a2.applyBackgroundUpdates = false; + a2.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + a3.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE; a4.userDisabled = true; a6.userDisabled = true; a9.userDisabled = false; for each (let addon in [a1, a2, a3, a4, a5, a6]) { oldSyncGUIDs[addon.id] = addon.syncGUID; } @@ -204,85 +206,85 @@ function test_results() { "addon9@tests.mozilla.org"], function([a1, a2, a3, a4, a5, a6, a7, a8, a9]) { // addon1 was enabled do_check_neq(a1, null); do_check_eq(a1.syncGUID, oldSyncGUIDs[a1.id]); do_check_false(a1.userDisabled); do_check_false(a1.appDisabled); do_check_true(a1.isActive); - do_check_true(a1.applyBackgroundUpdates); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); do_check_true(a1.foreignInstall); do_check_false(a1.hasBinaryComponents); do_check_false(a1.strictCompatibility); // addon2 was disabled do_check_neq(a2, null); do_check_eq(a2.syncGUID, oldSyncGUIDs[a2.id]); do_check_true(a2.userDisabled); do_check_false(a2.appDisabled); do_check_false(a2.isActive); - do_check_false(a2.applyBackgroundUpdates); + do_check_eq(a2.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_true(a2.foreignInstall); do_check_false(a2.hasBinaryComponents); do_check_false(a2.strictCompatibility); // addon3 was pending-disable in the database do_check_neq(a3, null); do_check_eq(a3.syncGUID, oldSyncGUIDs[a3.id]); do_check_true(a3.userDisabled); do_check_false(a3.appDisabled); do_check_false(a3.isActive); - do_check_true(a3.applyBackgroundUpdates); + do_check_eq(a3.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE); do_check_true(a3.foreignInstall); do_check_false(a3.hasBinaryComponents); do_check_false(a3.strictCompatibility); // addon4 was pending-enable in the database do_check_neq(a4, null); do_check_eq(a4.syncGUID, oldSyncGUIDs[a4.id]); do_check_false(a4.userDisabled); do_check_false(a4.appDisabled); do_check_true(a4.isActive); - do_check_true(a4.applyBackgroundUpdates); + do_check_eq(a4.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); do_check_true(a4.foreignInstall); do_check_false(a4.hasBinaryComponents); do_check_true(a4.strictCompatibility); // addon5 was enabled in the database but needed a compatibiltiy update do_check_neq(a5, null); do_check_false(a5.userDisabled); do_check_false(a5.appDisabled); do_check_true(a5.isActive); - do_check_true(a5.applyBackgroundUpdates); + do_check_eq(a4.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); do_check_true(a5.foreignInstall); do_check_false(a5.hasBinaryComponents); do_check_false(a5.strictCompatibility); // addon6 was disabled and compatible but a new version has been installed do_check_neq(a6, null); do_check_eq(a6.syncGUID, oldSyncGUIDs[a6.id]); do_check_eq(a6.version, "2.0"); do_check_true(a6.userDisabled); do_check_false(a6.appDisabled); do_check_false(a6.isActive); - do_check_true(a6.applyBackgroundUpdates); + do_check_eq(a6.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); do_check_true(a6.foreignInstall); do_check_eq(a6.sourceURI.spec, "http://localhost:4444/addons/test_migrate4_6.xpi"); do_check_eq(a6.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); do_check_false(a6.hasBinaryComponents); do_check_false(a6.strictCompatibility); // addon7 was installed manually do_check_neq(a7, null); do_check_eq(a7.version, "1.0"); do_check_false(a7.userDisabled); do_check_false(a7.appDisabled); do_check_true(a7.isActive); - do_check_true(a7.applyBackgroundUpdates); + do_check_eq(a7.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); do_check_false(a7.foreignInstall); do_check_eq(a7.sourceURI.spec, "http://localhost:4444/addons/test_migrate4_7.xpi"); do_check_eq(a7.releaseNotesURI, null); do_check_false(a7.hasBinaryComponents); do_check_false(a7.strictCompatibility); // addon8 was enabled and has binary components do_check_neq(a8, null);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrateAddonRepository.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrateAddonRepository.js @@ -1,13 +1,13 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -const EXPECTED_SCHEMA_VERSION = 2; +const EXPECTED_SCHEMA_VERSION = 3; let dbfile; function run_test() { do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); // Write out a minimal database. dbfile = gProfD.clone(); @@ -83,16 +83,34 @@ function run_test() { Services.obs.removeObserver(this, "addon-repository-shutdown"); // Check the DB schema has changed once AddonRepository has freed it. db = AM_Cc["@mozilla.org/storage/service;1"]. getService(AM_Ci.mozIStorageService). openDatabase(dbfile); do_check_eq(db.schemaVersion, EXPECTED_SCHEMA_VERSION); do_check_true(db.indexExists("developer_idx")); do_check_true(db.indexExists("screenshot_idx")); + do_check_true(db.indexExists("compatibility_override_idx")); + do_check_true(db.tableExists("compatibility_override")); + + // Check the trigger is working + db.executeSimpleSQL("INSERT INTO addon (id, type, name) VALUES('test_addon', 'extension', 'Test Addon')"); + let internalID = db.lastInsertRowID; + db.executeSimpleSQL("INSERT INTO compatibility_override (addon_internal_id, num, type) VALUES('" + internalID + "', '1', 'incompatible')"); + + let stmt = db.createStatement("SELECT COUNT(*) AS count FROM compatibility_override"); + stmt.executeStep(); + do_check_eq(stmt.row.count, 1); + stmt.reset(); + + db.executeSimpleSQL("DELETE FROM addon"); + stmt.executeStep(); + do_check_eq(stmt.row.count, 0); + stmt.finalize(); + db.close(); run_test_2(); } }, "addon-repository-shutdown", null); Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true); AddonRepository.getCachedAddonByID("test1@tests.mozilla.org", function (aAddon) { do_check_neq(aAddon, null);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -1,16 +1,18 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // This verifies that add-on update checks work const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; +const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); // This test requires lightweight themes update to be enabled even if the app // doesn't support lightweight themes. Services.prefs.setBoolPref("lightweightThemes.update.enabled", true); @@ -766,19 +768,20 @@ function run_test_12() { a4.uninstall(); restartManager(); run_test_13(); }); } -// Tests that no compatibility update is passed to the listener when there is +// Tests that a compatibility update is passed to the listener when there is // compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for. +// version of the app that the caller requested an update check for, when +// strict compatibility checking is disabled. function run_test_13() { // Not initially compatible but the update check will make it compatible writeInstallRDFForExtension({ id: "addon7@tests.mozilla.org", version: "1.0", updateURL: "http://localhost:4444/data/test_update.rdf", targetApplications: [{ id: "xpcshell@tests.mozilla.org", @@ -793,18 +796,18 @@ function run_test_13() { do_check_neq(a7, null); do_check_true(a7.isActive); do_check_true(a7.isCompatible); do_check_false(a7.appDisabled); do_check_true(a7.isCompatibleWith("0")); a7.findUpdates({ sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have not have seen compatibility information"); + onNoCompatibilityUpdateAvailable: function(addon) { + do_throw("Should have seen compatibility information"); }, onUpdateAvailable: function(addon, install) { do_throw("Should not have seen an available update"); }, onUpdateFinished: function(addon) { do_check_true(addon.isCompatible); @@ -1047,20 +1050,123 @@ function run_test_16() { AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { do_check_neq(a2.syncGUID, null); do_check_eq(oldGUID, a2.syncGUID); a2.uninstall(); restartManager(); - end_test(); + run_test_17(); }); } }); aInstall.install(); }, "application/x-xpinstall"); }); } }); aInstall.install(); }, "application/x-xpinstall"); } + +// Test that the update check correctly observes the +// extensions.strictCompatibility pref and compatibility overrides. +function run_test_17() { + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "3.0"); + }, + onDownloadFailed: function(aInstall) { + do_execute_soon(run_test_18); + } + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, "http://localhost:4444/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + // Fake a timer event + gInternalManager.notify(null); +} + +// Tests that compatibility updates are applied to addons when the updated +// compatibility data wouldn't match with strict compatibility enabled. +function run_test_18() { + writeInstallRDFForExtension({ + id: "addon10@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 10", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { + do_check_neq(a10, null); + + a10.findUpdates({ + onNoCompatibilityUpdateAvailable: function() { + do_throw("Should have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_test_19(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +// Test that the update check correctly observes when an addon opts-in to +// strict compatibility checking. +function run_test_19() { + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + end_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +}
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_compatmode.js @@ -0,0 +1,198 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that add-on update check correctly fills in the +// %COMPATIBILITY_MODE% token in the update URL. + + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +do_load_httpd_js(); +var testserver; +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +var COMPATIBILITY_PREF; + +function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + var channel = "default"; + try { + channel = Services.prefs.getCharPref("app.update.channel"); + } catch (e) { } + if (channel != "aurora" && + channel != "beta" && + channel != "release") { + var version = "nightly"; + } else { + version = Services.appinfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1"); + } + COMPATIBILITY_PREF = "extensions.checkCompatibility." + version; + + // Create and configure the HTTP server. + testserver = new nsHttpServer(); + testserver.registerDirectory("/data/", do_get_file("data")); + testserver.registerDirectory("/addons/", do_get_file("addons")); + testserver.start(4444); + + writeInstallRDFForExtension({ + id: "compatmode-normal@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_updatecompatmode_%COMPATIBILITY_MODE%.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon - normal" + }, profileDir); + + writeInstallRDFForExtension({ + id: "compatmode-strict@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_updatecompatmode_%COMPATIBILITY_MODE%.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon - strict" + }, profileDir); + + writeInstallRDFForExtension({ + id: "compatmode-strict-optin@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_updatecompatmode_%COMPATIBILITY_MODE%.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon - strict opt-in", + strictCompatibility: true + }, profileDir); + + writeInstallRDFForExtension({ + id: "compatmode-ignore@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_updatecompatmode_%COMPATIBILITY_MODE%.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon - ignore", + }, profileDir); + + startupManager(); + run_test_1(); +} + +function end_test() { + testserver.stop(do_test_finished); +} + + +// Strict compatibility checking disabled. +function run_test_1() { + do_print("Testing with strict compatibility checking disabled"); + Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); + AddonManager.getAddonByID("compatmode-normal@tests.mozilla.org", function(addon) { + do_check_neq(addon, null); + addon.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onNoUpdateAvailable: function() { + do_throw("Should have seen an available update"); + }, + + onUpdateAvailable: function(addon, install) { + do_check_eq(install.version, "2.0") + }, + + onUpdateFinished: function() { + run_test_2(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +// Strict compatibility checking enabled. +function run_test_2() { + do_print("Testing with strict compatibility checking enabled"); + Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); + AddonManager.getAddonByID("compatmode-strict@tests.mozilla.org", function(addon) { + do_check_neq(addon, null); + addon.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onNoUpdateAvailable: function() { + do_throw("Should have seen an available update"); + }, + + onUpdateAvailable: function(addon, install) { + do_check_eq(install.version, "2.0") + }, + + onUpdateFinished: function() { + run_test_3(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +// Strict compatibility checking opt-in. +function run_test_3() { + do_print("Testing with strict compatibility disabled, but addon opt-in"); + Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); + AddonManager.getAddonByID("compatmode-strict-optin@tests.mozilla.org", function(addon) { + do_check_neq(addon, null); + addon.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_test_4(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +// Compatibility checking disabled. +function run_test_4() { + do_print("Testing with all compatibility checking disabled"); + Services.prefs.setBoolPref(COMPATIBILITY_PREF, false); + AddonManager.getAddonByID("compatmode-ignore@tests.mozilla.org", function(addon) { + do_check_neq(addon, null); + addon.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onNoUpdateAvailable: function() { + do_throw("Should have seen an available update"); + }, + + onUpdateAvailable: function(addon, install) { + do_check_eq(install.version, "2.0") + }, + + onUpdateFinished: function() { + end_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +}
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that add-on update checks work correctly when compatibility +// check is disabled. + + +const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +do_load_httpd_js(); +var testserver; +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + + +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + // Create and configure the HTTP server. + testserver = new nsHttpServer(); + testserver.registerDirectory("/data/", do_get_file("data")); + testserver.registerDirectory("/addons/", do_get_file("addons")); + testserver.start(4444); + + run_test_1(); +} + +// Test that the update check correctly observes the +// extensions.strictCompatibility pref and compatibility overrides. +function run_test_1() { + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "4.0"); + }, + onDownloadFailed: function(aInstall) { + do_execute_soon(run_test_2); + } + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, "http://localhost:4444/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + // Fake a timer event + gInternalManager.notify(null); +} + +// Test that the update check correctly observes when an addon opts-in to +// strict compatibility checking. +function run_test_2() { + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onNoUpdateAvailable: function() { + do_throw("Should have seen an available update"); + }, + + onUpdateFinished: function() { + end_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -1,16 +1,18 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // This verifies that add-on update checks work const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; +const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); // This test requires lightweight themes update to be enabled even if the app // doesn't support lightweight themes. Services.prefs.setBoolPref("lightweightThemes.update.enabled", true); Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm"); @@ -1011,11 +1013,80 @@ function check_test_15(aInstall) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); a1.uninstall(); do_check_eq(a8, null); restartManager(); - end_test(); + run_test_16(); }); } + +// Test that the update check correctly observes the +// extensions.strictCompatibility pref and compatibility overrides. +function run_test_16() { + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "2.0"); + }, + onDownloadFailed: function(aInstall) { + do_execute_soon(run_test_17); + } + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, "http://localhost:4444/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + // Fake a timer event + gInternalManager.notify(null); +} + +// Test that the update check correctly observes when an addon opts-in to +// strict compatibility checking. +function run_test_17() { + + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:4444/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + end_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js @@ -1,16 +1,28 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // This verifies that AddonUpdateChecker works correctly Components.utils.import("resource://gre/modules/AddonUpdateChecker.jsm"); + +var channel = "default"; +try { + channel = Services.prefs.getCharPref("app.update.channel"); +} catch (e) { } +if (channel != "aurora" && channel != "beta" && channel != "release") + var version = "nightly"; +else + version = Services.appinfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1"); +const COMPATIBILITY_PREF = "extensions.checkCompatibility." + version; + + do_load_httpd_js(); var testserver; function run_test() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); // Create and configure the HTTP server. testserver = new nsHttpServer(); @@ -221,12 +233,92 @@ function run_test_11() { "extension", null, "http://localhost:4444/data/test_updatecheck.rdf", { onUpdateCheckComplete: function(updates) { do_throw("Update check should have failed"); }, onUpdateCheckError: function(status) { do_check_eq(status, AddonUpdateChecker.ERROR_PARSE_ERROR); - end_test(); + run_test_12(); + } + }); +} + +function run_test_12() { + AddonUpdateChecker.checkForUpdates("ignore-compat@tests.mozilla.org", + "extension", null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 3); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, + null, + null, + true); + do_check_neq(update, null); + do_check_eq(update.version, 2); + run_test_13(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); } }); } + +function run_test_13() { + AddonUpdateChecker.checkForUpdates("compat-override@tests.mozilla.org", + "extension", null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 3); + let overrides = [{ + type: "incompatible", + minVersion: 1, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 0.1, + appMaxVersion: 0.2 + }, { + type: "incompatible", + minVersion: 2, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 1, + appMaxVersion: 2 + }]; + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, + null, + null, + true, + false, + overrides); + do_check_neq(update, null); + do_check_eq(update.version, 1); + run_test_14(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_14() { + AddonUpdateChecker.checkForUpdates("compat-strict-optin@tests.mozilla.org", + "extension", null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 1); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, + null, + null, + true, + false); + do_check_eq(update, null); + end_test(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +}
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -4,16 +4,17 @@ tail = [test_AddonRepository.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_AddonRepository_cache.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_LightweightThemeManager.js] +[test_backgroundupdate.js] [test_badschema.js] [test_blocklistchange.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_bootstrap.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_bug299716.js] @@ -122,16 +123,17 @@ fail-if = os == "android" [test_bug619730.js] [test_bug620837.js] [test_bug655254.js] [test_bug659772.js] [test_bug675371.js] [test_cacheflush.js] [test_checkcompatibility.js] [test_ChromeManifestParser.js] +[test_compatoverrides.js] [test_corrupt.js] [test_corrupt_strictcompat.js] [test_dictionary.js] [test_disable.js] [test_distribution.js] [test_dss.js] # Bug 676992: test consistently fails on Android fail-if = os == "android" @@ -193,20 +195,24 @@ fail-if = os == "android" [test_types.js] [test_uninstall.js] [test_update.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_update_strictcompat.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" +[test_update_ignorecompat.js] +# Bug 676992: test consistently hangs on Android +skip-if = os == "android" [test_updatecheck.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_updateid.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" +[test_update_compatmode.js] [test_upgrade.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_upgrade_strictcompat.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android"
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js @@ -1,13 +1,14 @@ // ---------------------------------------------------------------------------- // Test whether passing a simple string to InstallTrigger.install throws an // exception function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); var triggers = encodeURIComponent(JSON.stringify(TESTROOT + "unsigned.xpi")); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); // Allow the in-page load handler to run first executeSoon(page_loaded); }, true);
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js @@ -1,13 +1,14 @@ // ---------------------------------------------------------------------------- // Test whether passing an undefined url InstallTrigger.install throws an // exception function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); var triggers = encodeURIComponent(JSON.stringify({ "Unsigned XPI": { URL: undefined } })); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() {
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js @@ -1,15 +1,16 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // ---------------------------------------------------------------------------- // Tests that cancelling multiple installs doesn't fail function test() { + ignoreAllUncaughtExceptions(); Harness.installConfirmCallback = confirm_install; Harness.installEndedCallback = install_ended; Harness.installsCompletedCallback = finish_test; Harness.setup(); var pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js @@ -1,11 +1,12 @@ // ---------------------------------------------------------------------------- // Test whether an InstallTrigger.install call fails when xpinstall is disabled function test() { + ignoreAllUncaughtExceptions(); Harness.installDisabledCallback = install_disabled; Harness.installBlockedCallback = allow_blocked; Harness.installConfirmCallback = confirm_install; Harness.setup(); Services.prefs.setBoolPref("xpinstall.enabled", false); var triggers = encodeURIComponent(JSON.stringify({
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js @@ -1,13 +1,14 @@ // ---------------------------------------------------------------------------- // Test whether an install fails if the url is a local file when requested from // web content function test() { waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"] .getService(Components.interfaces.nsIChromeRegistry); var chromeroot = getChromeRoot(gTestPath); try { var xpipath = cr.convertChromeURL(makeURI(chromeroot + "unsigned.xpi")).spec; } catch (ex) {
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_whitelist3.js +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_whitelist3.js @@ -1,12 +1,13 @@ // ---------------------------------------------------------------------------- // Tests installing an unsigned add-on through a navigation. Should not be // blocked since the referer is whitelisted. function test() { + ignoreAllUncaughtExceptions(); Harness.installConfirmCallback = confirm_install; Harness.installsCompletedCallback = finish_test; Harness.setup(); var pm = Services.perms; pm.add(makeURI("http://example.org/"), "install", pm.ALLOW_ACTION); var triggers = encodeURIComponent(JSON.stringify({
--- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2811,16 +2811,19 @@ XRE_main(int argc, char* argv[], const n // pass some basic info from the app data if (appData.vendor) CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Vendor"), nsDependentCString(appData.vendor)); if (appData.name) CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProductName"), nsDependentCString(appData.name)); + if (appData.ID) + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProductID"), + nsDependentCString(appData.ID)); if (appData.version) CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Version"), nsDependentCString(appData.version)); if (appData.buildID) CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("BuildID"), nsDependentCString(appData.buildID)); nsDependentCString releaseChannel(NS_STRINGIFY(MOZ_UPDATE_CHANNEL));
--- a/tools/profiler/sps/TableTicker.cpp +++ b/tools/profiler/sps/TableTicker.cpp @@ -287,32 +287,32 @@ void ProfileEntry::WriteTag(Profile *pro } } #endif } #define PROFILE_DEFAULT_ENTRY 100000 void mozilla_sampler_init() { - const char *val = PR_GetEnv("MOZ_PROFILER_SPS"); - if (!val || !*val) { - return; - } - // TODO linux port: Use TLS with ifdefs // TODO window port: See bug 683229 comment 15 // profiler uses getspecific because TLS is not supported on android. // getspecific was picked over nspr because it had less overhead required // to make the checkpoint function fast. if (pthread_key_create(&pkey_stack, NULL) || pthread_key_create(&pkey_ticker, NULL)) { LOG("Failed to init."); return; } + const char *val = PR_GetEnv("MOZ_PROFILER_SPS"); + if (!val || !*val) { + return; + } + TableTicker *t = new TableTicker(PROFILE_DEFAULT_ENTRY, 10); pthread_setspecific(pkey_ticker, t); pthread_setspecific(pkey_stack, t->GetStack()); t->Start(); } void mozilla_sampler_deinit()
--- a/tools/profiler/sps/sps_sampler.h +++ b/tools/profiler/sps/sps_sampler.h @@ -38,17 +38,16 @@ #include <pthread.h> #include "base/atomicops.h" #include "nscore.h" // TODO Merge into Sampler.h extern pthread_key_t pkey_stack; -extern pthread_key_t pkey_ticker; #define SAMPLER_INIT() mozilla_sampler_init(); #define SAMPLER_DEINIT() mozilla_sampler_deinit(); #define SAMPLE_CHECKPOINT(name_space, info) mozilla::SamplerStackFrameRAII only_one_sampleraii_per_scope(FULLFUNCTION, name_space "::" info); #define SAMPLE_MARKER(info) mozilla_sampler_add_marker(info); // STORE_SEQUENCER: Because signals can interrupt our profile modification // we need to make stores are not re-ordered by the compiler
--- a/tools/trace-malloc/lib/nsTraceMalloc.c +++ b/tools/trace-malloc/lib/nsTraceMalloc.c @@ -952,17 +952,17 @@ backtrace(tm_thread *t, int skip, int *i stack_buffer_info *info = &t->backtrace_buf; void ** new_stack_buffer; size_t new_stack_buffer_size; nsresult rv; t->suppress_tracing++; if (!stacks_enabled) { -#if defined(XP_MACOSX) && defined(__i386) +#if defined(XP_MACOSX) /* Walk the stack, even if stacks_enabled is false. We do this to check if we must set immediate_abort. */ info->entries = 0; rv = NS_StackWalk(stack_callback, skip, info); *immediate_abort = rv == NS_ERROR_UNEXPECTED; if (rv == NS_ERROR_UNEXPECTED || info->entries == 0) { t->suppress_tracing--; return NULL;
--- a/view/src/nsView.cpp +++ b/view/src/nsView.cpp @@ -433,17 +433,17 @@ void nsView::DoResetWidgetBounds(bool aM bool aInvalidateChangedSize) { // The geometry of a root view's widget is controlled externally, // NOT by sizing or positioning the view if (mViewManager->GetRootViewImpl() == this) { return; } nsIntRect curBounds; - mWindow->GetBounds(curBounds); + mWindow->GetClientBounds(curBounds); nsWindowType type; mWindow->GetWindowType(type); if (curBounds.IsEmpty() && mDimBounds.IsEmpty() && type == eWindowType_popup) { // Don't manipulate empty popup widgets. For example there's no point // moving hidden comboboxes around, or doing X server roundtrips // to compute their true screen position. This could mean that WidgetToScreen @@ -457,24 +457,26 @@ void nsView::DoResetWidgetBounds(bool aM nsIntRect newBounds = CalcWidgetBounds(type); bool changedPos = curBounds.TopLeft() != newBounds.TopLeft(); bool changedSize = curBounds.Size() != newBounds.Size(); // Child views are never attached to top level widgets, this is safe. if (changedPos) { if (changedSize && !aMoveOnly) { - mWindow->Resize(newBounds.x, newBounds.y, newBounds.width, newBounds.height, - aInvalidateChangedSize); + mWindow->ResizeClient(newBounds.x, newBounds.y, + newBounds.width, newBounds.height, + aInvalidateChangedSize); } else { - mWindow->Move(newBounds.x, newBounds.y); + mWindow->MoveClient(newBounds.x, newBounds.y); } } else { if (changedSize && !aMoveOnly) { - mWindow->Resize(newBounds.width, newBounds.height, aInvalidateChangedSize); + mWindow->ResizeClient(newBounds.width, newBounds.height, + aInvalidateChangedSize); } // else do nothing! } } void nsView::SetDimensions(const nsRect& aRect, bool aPaint, bool aResizeWidget) { nsRect dims = aRect; dims.MoveBy(mPosX, mPosY);
--- a/widget/public/nsIWidget.h +++ b/widget/public/nsIWidget.h @@ -113,18 +113,18 @@ typedef nsEventStatus (* EVENT_CALLBACK) #endif #ifdef XP_WIN #define NS_NATIVE_TSF_THREAD_MGR 100 #define NS_NATIVE_TSF_CATEGORY_MGR 101 #define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102 #endif #define NS_IWIDGET_IID \ - { 0x34460b01, 0x3dc2, 0x4b58, \ - { 0x8e, 0xd3, 0x7e, 0x7c, 0x33, 0xb5, 0x78, 0x8b } } + { 0x41fc0f2c, 0x65c2, 0x418e, \ + { 0x89, 0x91, 0x5f, 0x0c, 0xa7, 0x01, 0x05, 0x34 } } /* * Window shadow styles * Also used for the -moz-window-shadow CSS property */ #define NS_STYLE_WINDOW_SHADOW_NONE 0 #define NS_STYLE_WINDOW_SHADOW_DEFAULT 1 #define NS_STYLE_WINDOW_SHADOW_MENU 2 @@ -668,16 +668,31 @@ class nsIWidget : public nsISupports { * * @param aX the new x position expressed in the parent's coordinate system * @param aY the new y position expressed in the parent's coordinate system * **/ NS_IMETHOD Move(PRInt32 aX, PRInt32 aY) = 0; /** + * Reposition this widget so that the client area has the given offset. + * + * @param aX the new x offset of the client area expressed as an + * offset from the origin of the client area of the parent + * widget (for root widgets and popup widgets it is in + * screen coordinates) + * @param aY the new y offset of the client area expressed as an + * offset from the origin of the client area of the parent + * widget (for root widgets and popup widgets it is in + * screen coordinates) + * + **/ + NS_IMETHOD MoveClient(PRInt32 aX, PRInt32 aY) = 0; + + /** * Resize this widget. * * @param aWidth the new width expressed in the parent's coordinate system * @param aHeight the new height expressed in the parent's coordinate system * @param aRepaint whether the widget should be repainted * */ NS_IMETHOD Resize(PRInt32 aWidth, @@ -696,20 +711,39 @@ class nsIWidget : public nsISupports { */ NS_IMETHOD Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint) = 0; /** - * Resize and reposition the inner client area of the widget. + * Resize the widget so that the inner client area has the given size. + * + * @param aWidth the new width of the client area. + * @param aHeight the new height of the client area. + * @param aRepaint whether the widget should be repainted * - * @param aX the new x offset expressed in the parent's coordinate system - * @param aY the new y offset expressed in the parent's coordinate system + */ + NS_IMETHOD ResizeClient(PRInt32 aWidth, + PRInt32 aHeight, + bool aRepaint) = 0; + + /** + * Resize and reposition the widget so tht inner client area has the given + * offset and size. + * + * @param aX the new x offset of the client area expressed as an + * offset from the origin of the client area of the parent + * widget (for root widgets and popup widgets it is in + * screen coordinates) + * @param aY the new y offset of the client area expressed as an + * offset from the origin of the client area of the parent + * widget (for root widgets and popup widgets it is in + * screen coordinates) * @param aWidth the new width of the client area. * @param aHeight the new height of the client area. * @param aRepaint whether the widget should be repainted * */ NS_IMETHOD ResizeClient(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, @@ -794,19 +828,20 @@ class nsIWidget : public nsISupports { * includes any title bar on the window. * * @param aRect On return it holds the x, y, width and height of * this widget. */ NS_IMETHOD GetScreenBounds(nsIntRect &aRect) = 0; /** - * Get this widget's client area dimensions, if the window has a 3D - * border appearance this returns the area inside the border. Origin - * is always zero. + * Get this widget's client area bounds, if the window has a 3D border + * appearance this returns the area inside the border. The position is the + * position of the client area relative to the client area of the parent + * widget (for root widgets and popup widgets it is in screen coordinates). * * @param aRect On return it holds the x. y, width and height of * the client area of this widget. */ NS_IMETHOD GetClientBounds(nsIntRect &aRect) = 0; /** * Get the non-client area dimensions of the window.
--- a/widget/src/android/nsAppShell.cpp +++ b/widget/src/android/nsAppShell.cpp @@ -209,69 +209,85 @@ nsAppShell::ProcessNextNativeEvent(bool { EVLOG("nsAppShell::ProcessNextNativeEvent %d", mayWait); nsAutoPtr<AndroidGeckoEvent> curEvent; AndroidGeckoEvent *nextEvent; { MutexAutoLock lock(mCondLock); - curEvent = GetNextEvent(); + curEvent = PopNextEvent(); if (!curEvent && mayWait) { // hmm, should we really hardcode this 10s? #if defined(DEBUG_ANDROID_EVENTS) PRTime t0, t1; EVLOG("nsAppShell: waiting on mQueueCond"); t0 = PR_Now(); mQueueCond.Wait(PR_MillisecondsToInterval(10000)); t1 = PR_Now(); EVLOG("nsAppShell: wait done, waited %d ms", (int)(t1-t0)/1000); #else mQueueCond.Wait(); #endif - curEvent = GetNextEvent(); + curEvent = PopNextEvent(); } } if (!curEvent) return false; // Combine subsequent events of the same type nextEvent = PeekNextEvent(); while (nextEvent) { int curType = curEvent->Type(); int nextType = nextEvent->Type(); - // Do not skip draw events if the Java compositor is in use, since the Java compositor - // updates only the rect that changed - thus we will lose updates. -#ifndef MOZ_JAVA_COMPOSITOR - while (nextType == AndroidGeckoEvent::DRAW && + while (nextType == AndroidGeckoEvent::DRAW && mLastDrawEvent && mNumDraws > 1) { // skip this draw, since there's a later one already in the queue.. this will let us // deal with sequences that look like: // MOVE DRAW MOVE DRAW MOVE DRAW // and end up with just // MOVE DRAW // when we process all the events. - RemoveNextEvent(); + + // Combine the next draw event's rect with the last one in the queue + const nsIntRect& nextRect = nextEvent->Rect(); + const nsIntRect& lastRect = mLastDrawEvent->Rect(); + int combinedArea = (lastRect.width * lastRect.height) + + (nextRect.width * nextRect.height); + + nsIntRect combinedRect = lastRect.Union(nextRect); + mLastDrawEvent->Init(AndroidGeckoEvent::DRAW, combinedRect); + + // XXX We may want to consider using regions instead of rectangles. + // Print an error if we're upload a lot more than we would + // if we handled this as two separate events. + int boundsArea = combinedRect.width * combinedRect.height; + if (boundsArea > combinedArea * 8) + ALOG("nsAppShell::ProcessNextNativeEvent: " + "Area of bounds greatly exceeds combined area: %d > %d", + boundsArea, combinedArea); + + // Remove the next draw event + PopNextEvent(); delete nextEvent; #if defined(DEBUG_ANDROID_EVENTS) ALOG("# Removing DRAW event (%d outstanding)", mNumDraws); #endif nextEvent = PeekNextEvent(); nextType = nextEvent->Type(); } -#endif // If the next type of event isn't the same as the current type, // we don't coalesce. if (nextType != curType) break; // Can only coalesce motion move events, for motion events if (curType != AndroidGeckoEvent::MOTION_EVENT) @@ -280,18 +296,17 @@ nsAppShell::ProcessNextNativeEvent(bool if (!(curEvent->Action() == AndroidMotionEvent::ACTION_MOVE && nextEvent->Action() == AndroidMotionEvent::ACTION_MOVE)) break; #if defined(DEBUG_ANDROID_EVENTS) ALOG("# Removing % 2d event", curType); #endif - RemoveNextEvent(); - curEvent = nextEvent; + curEvent = PopNextEvent(); nextEvent = PeekNextEvent(); } EVLOG("nsAppShell: event %p %d [ndraws %d]", (void*)curEvent.get(), curEvent->Type(), mNumDraws); switch (curEvent->Type()) { case AndroidGeckoEvent::NATIVE_POKE: NativeEventCallback(); @@ -433,25 +448,26 @@ nsAppShell::ProcessNextNativeEvent(bool void nsAppShell::ResendLastResizeEvent(nsWindow* aDest) { if (gLastSizeChange) { nsWindow::OnGlobalAndroidEvent(gLastSizeChange); } } AndroidGeckoEvent* -nsAppShell::GetNextEvent() +nsAppShell::PopNextEvent() { AndroidGeckoEvent *ae = nsnull; MutexAutoLock lock(mQueueLock); if (mEventQueue.Length()) { ae = mEventQueue[0]; mEventQueue.RemoveElementAt(0); if (ae->Type() == AndroidGeckoEvent::DRAW) { - mNumDraws--; + if (--mNumDraws == 0) + mLastDrawEvent = nsnull; } } return ae; } AndroidGeckoEvent* nsAppShell::PeekNextEvent() @@ -483,36 +499,23 @@ nsAppShell::PostEvent(AndroidGeckoEvent } } } else { mEventQueue.AppendElement(ae); } if (ae->Type() == AndroidGeckoEvent::DRAW) { mNumDraws++; + mLastDrawEvent = ae; } } NotifyNativeEvent(); } void -nsAppShell::RemoveNextEvent() -{ - AndroidGeckoEvent *ae = nsnull; - MutexAutoLock lock(mQueueLock); - if (mEventQueue.Length()) { - ae = mEventQueue[0]; - mEventQueue.RemoveElementAt(0); - if (ae->Type() == AndroidGeckoEvent::DRAW) { - mNumDraws--; - } - } -} - -void nsAppShell::OnResume() { } nsresult nsAppShell::AddObserver(const nsAString &aObserverKey, nsIObserver *aObserver) { NS_ASSERTION(aObserver != nsnull, "nsAppShell::AddObserver: aObserver is null!");
--- a/widget/src/android/nsAppShell.h +++ b/widget/src/android/nsAppShell.h @@ -71,34 +71,34 @@ public: nsresult Init(); void NotifyNativeEvent(); virtual bool ProcessNextNativeEvent(bool mayWait); void PostEvent(mozilla::AndroidGeckoEvent *event); - void RemoveNextEvent(); void OnResume(); nsresult AddObserver(const nsAString &aObserverKey, nsIObserver *aObserver); void CallObserver(const nsAString &aObserverKey, const nsAString &aTopic, const nsAString &aData); void RemoveObserver(const nsAString &aObserverKey); void NotifyObservers(nsISupports *aSupports, const char *aTopic, const PRUnichar *aData); void ResendLastResizeEvent(nsWindow* aDest); protected: virtual void ScheduleNativeEventCallback(); virtual ~nsAppShell(); Mutex mQueueLock; Mutex mCondLock; CondVar mQueueCond; int mNumDraws; + mozilla::AndroidGeckoEvent *mLastDrawEvent; nsTArray<mozilla::AndroidGeckoEvent *> mEventQueue; nsInterfaceHashtable<nsStringHashKey, nsIObserver> mObserversHash; - mozilla::AndroidGeckoEvent *GetNextEvent(); + mozilla::AndroidGeckoEvent *PopNextEvent(); mozilla::AndroidGeckoEvent *PeekNextEvent(); }; #endif // nsAppShell_h__
--- a/widget/src/gtk2/nsWindow.cpp +++ b/widget/src/gtk2/nsWindow.cpp @@ -1575,16 +1575,67 @@ nsWindow::GetScreenBounds(nsIntRect &aRe // with Resize. aRect.SizeTo(mBounds.Size()); LOG(("GetScreenBounds %d,%d | %dx%d\n", aRect.x, aRect.y, aRect.width, aRect.height)); return NS_OK; } NS_IMETHODIMP +nsWindow::GetClientBounds(nsIntRect &aRect) +{ + // GetBounds returns a rect whose top left represents the top left of the + // outer bounds, but whose width/height represent the size of the inner + // bounds (which is messed up). + GetBounds(aRect); + aRect.MoveBy(GetClientOffset()); + + return NS_OK; +} + +nsIntPoint +nsWindow::GetClientOffset() +{ + if (!mIsTopLevel) { + return nsIntPoint(0, 0); + } + + GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); + + GdkAtom type_returned; + int format_returned; + int length_returned; + long *frame_extents; + + if (!mShell || !mShell->window || + !gdk_property_get(mShell->window, + gdk_atom_intern ("_NET_FRAME_EXTENTS", FALSE), + cardinal_atom, + 0, // offset + 4*4, // length + FALSE, // delete + &type_returned, + &format_returned, + &length_returned, + (guchar **) &frame_extents) || + length_returned/sizeof(glong) != 4) { + + return nsIntPoint(0, 0); + } + + // data returned is in the order left, right, top, bottom + PRInt32 left = PRInt32(frame_extents[0]); + PRInt32 top = PRInt32(frame_extents[2]); + + g_free(frame_extents); + + return nsIntPoint(left, top); +} + +NS_IMETHODIMP nsWindow::SetForegroundColor(const nscolor &aColor) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsWindow::SetBackgroundColor(const nscolor &aColor) { @@ -2334,27 +2385,26 @@ nsWindow::OnConfigureEvent(GtkWidget *aW // the client window. // // Override-redirect windows are children of the root window so parent // coordinates are root coordinates. LOG(("configure event [%p] %d %d %d %d\n", (void *)this, aEvent->x, aEvent->y, aEvent->width, aEvent->height)); - // mBounds.x/y are set to the window manager frame top-left when Move() or - // Resize()d from within Gecko, so comparing with the client window - // top-left is weird. However, mBounds.x/y are set to client window - // position below, so this check avoids unwanted rollup on spurious - // configure events from Cygwin/X (bug 672103). - if (mBounds.x == aEvent->x && - mBounds.y == aEvent->y) - return FALSE; + nsIntRect screenBounds; + GetScreenBounds(screenBounds); if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) { - check_for_rollup(aEvent->window, 0, 0, false, true); + // This check avoids unwanted rollup on spurious configure events from + // Cygwin/X (bug 672103). + if (mBounds.x != screenBounds.x || + mBounds.y != screenBounds.y) { + check_for_rollup(aEvent->window, 0, 0, false, true); + } } // This event indicates that the window position may have changed. // mBounds.Size() is updated in OnSizeAllocate(). // (The gtk_window_get_window_type() function is only available from // version 2.20.) NS_ASSERTION(GTK_IS_WINDOW(aWidget), @@ -2372,21 +2422,17 @@ nsWindow::OnConfigureEvent(GtkWidget *aW // // Skipping the NS_MOVE dispatch saves context menus from an infinite // loop when nsXULPopupManager::PopupMoved moves the window to the new // position and nsMenuPopupFrame::SetPopupPosition adds // offsetForContextMenu on each iteration. return FALSE; } - // This is wrong, but noautohide titlebar panels currently depend on it - // (bug 601545#c13). mBounds.TopLeft() should refer to the - // window-manager frame top-left, but WidgetToScreenOffset() gives the - // client window origin. - mBounds.MoveTo(WidgetToScreenOffset()); + mBounds.MoveTo(screenBounds.TopLeft()); nsGUIEvent event(true, NS_MOVE, this); event.refPoint = mBounds.TopLeft(); // XXX mozilla will invalidate the entire window after this move // complete. wtf? nsEventStatus status;
--- a/widget/src/gtk2/nsWindow.h +++ b/widget/src/gtk2/nsWindow.h @@ -165,16 +165,18 @@ public: NS_IMETHOD PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, nsIWidget *aWidget, bool aActivate); NS_IMETHOD SetZIndex(PRInt32 aZIndex); NS_IMETHOD SetSizeMode(PRInt32 aMode); NS_IMETHOD Enable(bool aState); NS_IMETHOD SetFocus(bool aRaise = false); NS_IMETHOD GetScreenBounds(nsIntRect &aRect); + NS_IMETHOD GetClientBounds(nsIntRect &aRect); + virtual nsIntPoint GetClientOffset(); NS_IMETHOD SetForegroundColor(const nscolor &aColor); NS_IMETHOD SetBackgroundColor(const nscolor &aColor); NS_IMETHOD SetCursor(nsCursor aCursor); NS_IMETHOD SetCursor(imgIContainer* aCursor, PRUint32 aHotspotX, PRUint32 aHotspotY); NS_IMETHOD Invalidate(const nsIntRect &aRect, bool aIsSynchronous); NS_IMETHOD Update();
--- a/widget/src/os2/nsWindow.cpp +++ b/widget/src/os2/nsWindow.cpp @@ -764,20 +764,17 @@ NS_METHOD nsWindow::GetBounds(nsIntRect& } //----------------------------------------------------------------------------- // Since mBounds contains the dimensions of the client, os2FrameWindow // doesn't have to provide any special handling for this method. NS_METHOD nsWindow::GetClientBounds(nsIntRect& aRect) { - aRect.x = 0; - aRect.y = 0; - aRect.width = mBounds.width; - aRect.height = mBounds.height; + aRect = mBounds; return NS_OK; } //----------------------------------------------------------------------------- nsIntPoint nsWindow::WidgetToScreenOffset() { POINTL point = { 0, 0 };
--- a/widget/src/windows/nsNativeThemeWin.cpp +++ b/widget/src/windows/nsNativeThemeWin.cpp @@ -84,19 +84,21 @@ static inline bool IsHTMLContent(nsIFram } static PRInt32 GetTopLevelWindowActiveState(nsIFrame *aFrame) { // Get the widget. nsIFrame's GetNearestWidget walks up the view chain // until it finds a real window. nsIWidget* widget = aFrame->GetNearestWidget(); nsWindow * window = static_cast<nsWindow*>(widget); + if (!window) + return mozilla::widget::themeconst::FS_INACTIVE; if (widget && !window->IsTopLevelWidget() && !(window = window->GetParentWindow(false))) - return false; + return mozilla::widget::themeconst::FS_INACTIVE; if (window->GetWindowHandle() == ::GetActiveWindow()) return mozilla::widget::themeconst::FS_ACTIVE; return mozilla::widget::themeconst::FS_INACTIVE; } static PRInt32 GetWindowFrameButtonState(nsIFrame *aFrame, nsEventStates eventState) {
--- a/widget/src/windows/nsTextStore.cpp +++ b/widget/src/windows/nsTextStore.cpp @@ -1180,16 +1180,18 @@ nsTextStore::GetScreenExt(TsViewCookie v // Result rect is in top level widget coordinates refWindow = refWindow->GetTopLevelWindow(false); NS_ENSURE_TRUE(refWindow, E_FAIL); nsIntRect boundRect; nsresult rv = refWindow->GetClientBounds(boundRect); NS_ENSURE_SUCCESS(rv, E_FAIL); + boundRect.MoveTo(0, 0); + // Clip frame rect to window rect boundRect.IntersectRect(event.mReply.mRect, boundRect); if (!boundRect.IsEmpty()) { boundRect.MoveBy(refWindow->WidgetToScreenOffset()); ::SetRect(prc, boundRect.x, boundRect.y, boundRect.XMost(), boundRect.YMost()); } else { ::SetRectEmpty(prc);
--- a/widget/src/windows/nsWindow.cpp +++ b/widget/src/windows/nsWindow.cpp @@ -1518,40 +1518,16 @@ NS_METHOD nsWindow::Resize(PRInt32 aX, P } if (aRepaint) Invalidate(false); return NS_OK; } -// Resize the client area and position the widget within it's parent -NS_METHOD nsWindow::ResizeClient(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint) -{ - NS_ASSERTION((aWidth >=0) , "Negative width passed to ResizeClient"); - NS_ASSERTION((aHeight >=0), "Negative height passed to ResizeClient"); - - // Adjust our existing window bounds, based on the new client dims. - RECT client; - GetClientRect(mWnd, &client); - nsIntPoint dims(client.right - client.left, client.bottom - client.top); - aWidth = mBounds.width + (aWidth - dims.x); - aHeight = mBounds.height + (aHeight - dims.y); - - if (aX || aY) { - // offsets - nsIntRect bounds; - GetScreenBounds(bounds); - aX += bounds.x; - aY += bounds.y; - return Resize(aX, aY, aWidth, aHeight, aRepaint); - } - return Resize(aWidth, aHeight, aRepaint); -} - NS_IMETHODIMP nsWindow::BeginResizeDrag(nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical) { NS_ENSURE_ARG_POINTER(aEvent); if (aEvent->eventStructType != NS_MOUSE_EVENT) { // you can only begin a resize drag with a mouse event return NS_ERROR_INVALID_ARG; @@ -1925,19 +1901,19 @@ NS_METHOD nsWindow::GetBounds(nsIntRect // Get this component dimension NS_METHOD nsWindow::GetClientBounds(nsIntRect &aRect) { if (mWnd) { RECT r; VERIFY(::GetClientRect(mWnd, &r)); - // assign size - aRect.x = 0; - aRect.y = 0; + nsIntRect bounds; + GetBounds(bounds); + aRect.MoveTo(bounds.TopLeft() + GetClientOffset()); aRect.width = r.right - r.left; aRect.height = r.bottom - r.top; } else { aRect.SetRect(0,0,0,0); } return NS_OK; }
--- a/widget/src/windows/nsWindow.h +++ b/widget/src/windows/nsWindow.h @@ -120,17 +120,16 @@ public: virtual nsIWidget* GetParent(void); virtual float GetDPI(); NS_IMETHOD Show(bool bState); NS_IMETHOD IsVisible(bool & aState); NS_IMETHOD ConstrainPosition(bool aAllowSlop, PRInt32 *aX, PRInt32 *aY); NS_IMETHOD Move(PRInt32 aX, PRInt32 aY); NS_IMETHOD Resize(PRInt32 aWidth, PRInt32 aHeight, bool aRepaint); NS_IMETHOD Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint); - NS_IMETHOD ResizeClient(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint); NS_IMETHOD BeginResizeDrag(nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical); NS_IMETHOD PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, nsIWidget *aWidget, bool aActivate); NS_IMETHOD SetSizeMode(PRInt32 aMode); NS_IMETHOD Enable(bool aState); NS_IMETHOD IsEnabled(bool *aState); NS_IMETHOD SetFocus(bool aRaise); NS_IMETHOD GetBounds(nsIntRect &aRect); NS_IMETHOD GetScreenBounds(nsIntRect &aRect);
--- a/widget/src/xpwidgets/nsBaseWidget.cpp +++ b/widget/src/xpwidgets/nsBaseWidget.cpp @@ -295,25 +295,16 @@ ViewWrapper* nsBaseWidget::GetAttachedVi } NS_IMETHODIMP nsBaseWidget::SetAttachedViewPtr(ViewWrapper* aViewWrapper) { mViewWrapperPtr = aViewWrapper; return NS_OK; } -NS_METHOD nsBaseWidget::ResizeClient(PRInt32 aX, - PRInt32 aY, - PRInt32 aWidth, - PRInt32 aHeight, - bool aRepaint) -{ - return Resize(aX, aY, aWidth, aHeight, aRepaint); -} - //------------------------------------------------------------------------- // // Close this nsBaseWidget // //------------------------------------------------------------------------- NS_METHOD nsBaseWidget::Destroy() { // Just in case our parent is the only ref to us @@ -914,16 +905,60 @@ void nsBaseWidget::OnDestroy() NS_IF_RELEASE(mContext); } NS_METHOD nsBaseWidget::SetWindowClass(const nsAString& xulWinType) { return NS_ERROR_NOT_IMPLEMENTED; } +NS_METHOD nsBaseWidget::MoveClient(PRInt32 aX, PRInt32 aY) +{ + nsIntPoint clientOffset(GetClientOffset()); + aX -= clientOffset.x; + aY -= clientOffset.y; + return Move(aX, aY); +} + +NS_METHOD nsBaseWidget::ResizeClient(PRInt32 aWidth, + PRInt32 aHeight, + bool aRepaint) +{ + NS_ASSERTION((aWidth >=0) , "Negative width passed to ResizeClient"); + NS_ASSERTION((aHeight >=0), "Negative height passed to ResizeClient"); + + nsIntRect clientBounds; + GetClientBounds(clientBounds); + aWidth = mBounds.width + (aWidth - clientBounds.width); + aHeight = mBounds.height + (aHeight - clientBounds.height); + + return Resize(aWidth, aHeight, aRepaint); +} + +NS_METHOD nsBaseWidget::ResizeClient(PRInt32 aX, + PRInt32 aY, + PRInt32 aWidth, + PRInt32 aHeight, + bool aRepaint) +{ + NS_ASSERTION((aWidth >=0) , "Negative width passed to ResizeClient"); + NS_ASSERTION((aHeight >=0), "Negative height passed to ResizeClient"); + + nsIntRect clientBounds; + GetClientBounds(clientBounds); + aWidth = mBounds.width + (aWidth - clientBounds.width); + aHeight = mBounds.height + (aHeight - clientBounds.height); + + nsIntPoint clientOffset(GetClientOffset()); + aX -= clientOffset.x; + aY -= clientOffset.y; + + return Resize(aX, aY, aWidth, aHeight, aRepaint); +} + //------------------------------------------------------------------------- // // Bounds // //------------------------------------------------------------------------- /** * If the implementation of nsWindow supports borders this method MUST be overridden
--- a/widget/src/xpwidgets/nsBaseWidget.h +++ b/widget/src/xpwidgets/nsBaseWidget.h @@ -117,20 +117,25 @@ public: LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT, bool* aAllowRetaining = nsnull); virtual void DrawOver(LayerManager* aManager, nsIntRect aRect) {} virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) {} virtual gfxASurface* GetThebesSurface(); NS_IMETHOD SetModal(bool aModal); NS_IMETHOD SetWindowClass(const nsAString& xulWinType); + NS_IMETHOD MoveClient(PRInt32 aX, PRInt32 aY); + NS_IMETHOD ResizeClient(PRInt32 aWidth, PRInt32 aHeight, bool aRepaint); + NS_IMETHOD ResizeClient(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint); NS_IMETHOD SetBounds(const nsIntRect &aRect); NS_IMETHOD GetBounds(nsIntRect &aRect); NS_IMETHOD GetClientBounds(nsIntRect &aRect); NS_IMETHOD GetScreenBounds(nsIntRect &aRect); + NS_IMETHOD GetNonClientMargins(nsIntMargin &margins); + NS_IMETHOD SetNonClientMargins(nsIntMargin &margins); virtual nsIntPoint GetClientOffset(); NS_IMETHOD EnableDragDrop(bool aEnable); NS_IMETHOD GetAttention(PRInt32 aCycleCount); virtual bool HasPendingInputEvent(); NS_IMETHOD SetIcon(const nsAString &anIconSpec); NS_IMETHOD BeginSecureKeyboardInput(); NS_IMETHOD EndSecureKeyboardInput(); NS_IMETHOD SetWindowTitlebarColor(nscolor aColor, bool aActive); @@ -158,19 +163,16 @@ public: EVENT_CALLBACK aHandleEventFunction, nsDeviceContext *aContext, nsWidgetInitData *aInitData = nsnull, bool aForceUseIWidgetParent = false); NS_IMETHOD SetEventCallback(EVENT_CALLBACK aEventFunction, nsDeviceContext *aContext); NS_IMETHOD AttachViewToTopLevel(EVENT_CALLBACK aViewEventFunction, nsDeviceContext *aContext); virtual ViewWrapper* GetAttachedViewPtr(); NS_IMETHOD SetAttachedViewPtr(ViewWrapper* aViewWrapper); - NS_IMETHOD ResizeClient(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint); - NS_IMETHOD GetNonClientMargins(nsIntMargin &margins); - NS_IMETHOD SetNonClientMargins(nsIntMargin &margins); NS_IMETHOD RegisterTouchWindow(); NS_IMETHOD UnregisterTouchWindow(); nsPopupLevel PopupLevel() { return mPopupLevel; } virtual nsIntSize ClientToWindowSize(const nsIntSize& aClientSize) { return aClientSize;
--- a/xpcom/base/nsStackWalk.cpp +++ b/xpcom/base/nsStackWalk.cpp @@ -36,19 +36,157 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* API for getting a stack trace of the C/C++ stack on the current thread */ #include "mozilla/Util.h" +#include "nsDebug.h" +#include "nsStackWalkPrivate.h" #include "nsStackWalk.h" +// The presence of this address is the stack must stop the stack walk. If +// there is no such address, the structure will be {NULL, true}. +struct CriticalAddress { + void* mAddr; + bool mInit; +}; +static CriticalAddress gCriticalAddress; + +#if defined(HAVE_DLOPEN) || defined(XP_MACOSX) +#include <dlfcn.h> +#endif + +#ifdef XP_MACOSX +#include <pthread.h> +#include <errno.h> +#include <CoreServices/CoreServices.h> + +typedef void +malloc_logger_t(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, + uintptr_t result, uint32_t num_hot_frames_to_skip); +extern malloc_logger_t *malloc_logger; + +static void +stack_callback(void *pc, void *closure) +{ + const char *name = reinterpret_cast<char *>(closure); + Dl_info info; + + // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The + // stack shows up as having two pthread_cond_wait$UNIX2003 frames. The + // correct one is the first that we find on our way up, so the + // following check for gCriticalAddress.mAddr is critical. + if (gCriticalAddress.mAddr || dladdr(pc, &info) == 0 || + info.dli_sname == NULL || strcmp(info.dli_sname, name) != 0) + return; + gCriticalAddress.mAddr = pc; +} + +#define MAC_OS_X_VERSION_10_7_HEX 0x00001070 +#define MAC_OS_X_VERSION_10_6_HEX 0x00001060 + +static PRInt32 OSXVersion() +{ + static PRInt32 gOSXVersion = 0x0; + if (gOSXVersion == 0x0) { + OSErr err = ::Gestalt(gestaltSystemVersion, (SInt32*)&gOSXVersion); + MOZ_ASSERT(err == noErr); + } + return gOSXVersion; +} + +static bool OnLionOrLater() +{ + return (OSXVersion() >= MAC_OS_X_VERSION_10_7_HEX); +} + +static bool OnSnowLeopardOrLater() +{ + return (OSXVersion() >= MAC_OS_X_VERSION_10_6_HEX); +} + +static void +my_malloc_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, + uintptr_t result, uint32_t num_hot_frames_to_skip) +{ + static bool once = false; + if (once) + return; + once = true; + + // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The + // stack shows up as having two pthread_cond_wait$UNIX2003 frames. + const char *name = OnSnowLeopardOrLater() ? "new_sem_from_pool" : + "pthread_cond_wait$UNIX2003"; + NS_StackWalk(stack_callback, 0, const_cast<char*>(name)); +} + +void +StackWalkInitCriticalAddress() +{ + if(gCriticalAddress.mInit) + return; + gCriticalAddress.mInit = true; + // We must not do work when 'new_sem_from_pool' calls realloc, since + // it holds a non-reentrant spin-lock and we will quickly deadlock. + // new_sem_from_pool is not directly accessible using dlsym, so + // we force a situation where new_sem_from_pool is on the stack and + // use dladdr to check the addresses. + + MOZ_ASSERT(malloc_logger == NULL); + malloc_logger = my_malloc_logger; + + pthread_cond_t cond; + int r = pthread_cond_init(&cond, 0); + MOZ_ASSERT(r == 0); + pthread_mutex_t mutex; + r = pthread_mutex_init(&mutex,0); + MOZ_ASSERT(r == 0); + r = pthread_mutex_lock(&mutex); + MOZ_ASSERT(r == 0); + struct timespec abstime = {0, 1}; + r = pthread_cond_timedwait_relative_np(&cond, &mutex, &abstime); + malloc_logger = NULL; + + // On Lion, malloc is no longer called from pthread_cond_*wait*. This prevents + // us from finding the address, but that is fine, since with no call to malloc + // there is no critical address. + MOZ_ASSERT(OnLionOrLater() || gCriticalAddress.mAddr != NULL); + MOZ_ASSERT(r == ETIMEDOUT); + r = pthread_mutex_unlock(&mutex); + MOZ_ASSERT(r == 0); + r = pthread_mutex_destroy(&mutex); + MOZ_ASSERT(r == 0); + r = pthread_cond_destroy(&cond); + MOZ_ASSERT(r == 0); +} + +static bool IsCriticalAddress(void* aPC) +{ + return gCriticalAddress.mAddr == aPC; +} +#else +static bool IsCriticalAddress(void* aPC) +{ + return false; +} +// We still initialize gCriticalAddress.mInit so that this code behaves +// the same on all platforms. Otherwise a failure to init would be visible +// only on OS X. +void +StackWalkInitCriticalAddress() +{ + gCriticalAddress.mInit = true; +} +#endif + #if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_IA64)) // WIN32 x86 stack walking code #include "nscore.h" #include <windows.h> #include <process.h> #include <stdio.h> #include "plstr.h" #include "mozilla/FunctionTimer.h" @@ -650,16 +788,17 @@ WalkStackThread(void* aData) * Otherwise StackWalk will return FALSE when it hits a frame in a DLL * whose in memory address doesn't match its in-file address. */ EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); HANDLE myProcess, myThread; DWORD walkerReturn; struct WalkStackData data; if (!EnsureImageHlpInitialized()) return false; // Have to duplicate handle to get a real handle. @@ -1135,22 +1274,16 @@ NS_FormatCodeAddressDetails(void *aPC, c // On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed // if __USE_GNU is defined. I suppose its some kind of standards // adherence thing. // #if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU) #define __USE_GNU #endif -#if defined(HAVE_DLOPEN) || defined(XP_MACOSX) -#include <dlfcn.h> -#endif - - - // This thing is exported by libstdc++ // Yes, this is a gcc only hack #if defined(MOZ_DEMANGLE_SYMBOLS) #include <cxxabi.h> #include <stdlib.h> // for free() #endif // MOZ_DEMANGLE_SYMBOLS void DemangleSymbol(const char * aSymbol, @@ -1343,16 +1476,17 @@ cs_operate(int (*operate_func)(void *, v { cswalkstack(csgetframeptr(), operate_func, usrarg); } EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); struct my_user_args args; if (!initialized) myinit(); args.callback = aCallback; args.skipFrames = aSkipFrames; /* XXX Not handled! */ args.closure = aClosure; @@ -1417,68 +1551,22 @@ NS_FormatCodeAddressDetails(void *aPC, c #else #define HAVE___LIBC_STACK_END 0 #endif #if HAVE___LIBC_STACK_END extern void *__libc_stack_end; // from ld-linux.so #endif -#ifdef XP_MACOSX -struct AddressRange { - void* mStart; - void* mEnd; -}; -// Addresses in this range must stop the stack walk -static AddressRange gCriticalRange; - -static void FindFunctionAddresses(const char* aName, AddressRange* aRange) -{ - aRange->mStart = dlsym(RTLD_DEFAULT, aName); - if (!aRange->mStart) - return; - aRange->mEnd = aRange->mStart; - while (true) { - Dl_info info; - if (!dladdr(aRange->mEnd, &info)) - break; - if (strcmp(info.dli_sname, aName)) - break; - aRange->mEnd = (char*)aRange->mEnd + 1; - } -} - -static void InitCriticalRanges() -{ - if (gCriticalRange.mStart) - return; - // We must not do work when 'new_sem_from_pool' calls realloc, since - // it holds a non-reentrant spin-lock and we will quickly deadlock. - // new_sem_from_pool is not directly accessible using dladdr but its - // code is bundled with pthread_cond_wait$UNIX2003 (on - // Leopard anyway). - FindFunctionAddresses("pthread_cond_wait$UNIX2003", &gCriticalRange); -} - -static bool InCriticalRange(void* aPC) -{ - return gCriticalRange.mStart && - gCriticalRange.mStart <= aPC && aPC < gCriticalRange.mEnd; -} -#else -static void InitCriticalRanges() {} -static bool InCriticalRange(void* aPC) { return false; } -#endif - EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); // Stack walking code courtesy Kipp's "leaky". - InitCriticalRanges(); // Get the frame pointer void **bp; #if defined(__i386) __asm__( "movl %%ebp, %0" : "=g"(bp)); #else // It would be nice if this worked uniformly, but at least on i386 and // x86_64, it stopped working with gcc 4.1, because it points to the @@ -1501,18 +1589,18 @@ NS_StackWalk(NS_WalkStackCallback aCallb break; } #if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) // ppc mac or powerpc64 linux void *pc = *(bp+2); #else // i386 or powerpc32 linux void *pc = *(bp+1); #endif - if (InCriticalRange(pc)) { - printf("Aborting stack trace, PC in critical range\n"); + if (IsCriticalAddress(pc)) { + printf("Aborting stack trace, PC is critical\n"); return NS_ERROR_UNEXPECTED; } if (--skip < 0) { (*aCallback)(pc, aClosure); } bp = next; } return NS_OK; @@ -1528,34 +1616,42 @@ struct unwind_info { int skip; void *closure; }; static _Unwind_Reason_Code unwind_callback (struct _Unwind_Context *context, void *closure) { unwind_info *info = static_cast<unwind_info *>(closure); - if (--info->skip < 0) { - void *pc = reinterpret_cast<void *>(_Unwind_GetIP(context)); + void *pc = reinterpret_cast<void *>(_Unwind_GetIP(context)); + if (IsCriticalAddress(pc)) { + printf("Aborting stack trace, PC is critical\n"); + /* We just want to stop the walk, so any error code will do. + Using _URC_NORMAL_STOP would probably be the most accurate, + but it is not defined on Android for ARM. */ + return _URC_FOREIGN_EXCEPTION_CAUGHT; + } + if (--info->skip < 0) (*info->callback)(pc, info->closure); - } return _URC_NO_REASON; } EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); unwind_info info; info.callback = aCallback; info.skip = aSkipFrames + 1; info.closure = aClosure; - _Unwind_Backtrace(unwind_callback, &info); - + _Unwind_Reason_Code t = _Unwind_Backtrace(unwind_callback, &info); + if (t != _URC_END_OF_STACK) + return NS_ERROR_UNEXPECTED; return NS_OK; } #endif EXPORT_XPCOM_API(nsresult) NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) { @@ -1615,16 +1711,17 @@ NS_FormatCodeAddressDetails(void *aPC, c #endif #else // unsupported platform. EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); return NS_ERROR_NOT_IMPLEMENTED; } EXPORT_XPCOM_API(nsresult) NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) { aDetails->library[0] = '\0'; aDetails->loffset = 0;
new file mode 100644 --- /dev/null +++ b/xpcom/base/nsStackWalkPrivate.h @@ -0,0 +1,43 @@ +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is NS_WalkTheStack. + * + * The Initial Developer of the Original Code is the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mozilla Corporation (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Initialize the critical sections for this platform so that we can + * abort stack walks when needed. + */ +void +StackWalkInitCriticalAddress(void);
--- a/xpcom/base/nsTraceRefcntImpl.cpp +++ b/xpcom/base/nsTraceRefcntImpl.cpp @@ -45,16 +45,17 @@ #include "prprf.h" #include "prlog.h" #include "plstr.h" #include "prlink.h" #include <stdlib.h> #include "nsCOMPtr.h" #include "nsCRT.h" #include <math.h> +#include "nsStackWalkPrivate.h" #include "nsStackWalk.h" #include "nsString.h" #include "nsXULAppAPI.h" #ifdef XP_WIN #include <process.h> #define getpid _getpid #else @@ -918,16 +919,18 @@ nsTraceRefcntImpl::DemangleSymbol(const } //---------------------------------------------------------------------- EXPORT_XPCOM_API(void) NS_LogInit() { + // FIXME: This is called multiple times, we should probably not allow that. + StackWalkInitCriticalAddress(); #ifdef NS_IMPL_REFCNT_LOGGING if (++gInitCount) nsTraceRefcntImpl::SetActivityIsLegal(true); #endif #ifdef NS_TRACE_MALLOC // XXX we don't have to worry about shutting down trace-malloc; it // handles this itself, through an atexit() callback.
deleted file mode 100644 --- a/xpinstall/Makefile.in +++ /dev/null @@ -1,57 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is Mozilla Communicator client code, released -# March 31, 1998. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Daniel Veditz <dveditz@netscape.com> -# Douglas Turner <dougt@netscape.com> -# Samir Gehani <sgehani@netscape.com> -# Dave Townsend <dtownsend@oxymoronical.com> -# -# Alternatively, the contents of this file may be used under the terms of -# either of the GNU General Public License Version 2 or later (the "GPL"), -# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** - -DEPTH = .. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -MODULE = xpinstall -DIRS = public src - -ifdef ENABLE_TESTS -DIRS += tests -endif - -include $(topsrcdir)/config/rules.mk
deleted file mode 100644 --- a/xpinstall/public/Makefile.in +++ /dev/null @@ -1,65 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is Mozilla Communicator client code, released -# March 31, 1998. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Daniel Veditz <dveditz@netscape.com> -# Douglas Turner <dougt@netscape.com> -# Dave Townsend <dtownsend@oxymoronical.com> -# -# Alternatively, the contents of this file may be used under the terms of -# either of the GNU General Public License Version 2 or later (the "GPL"), -# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** - -DEPTH = ../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -MODULE = xpinstall -GRE_MODULE = 1 - -XPIDLSRCS = \ - nsIXPIDialogService.idl \ - nsIXPIProgressDialog.idl \ - nsIXPInstallManager.idl \ - nsIXPIInstallInfo.idl \ - nsPICertNotification.idl \ - $(NULL) - -EXPORTS = \ - nsIDOMInstallTriggerGlobal.h \ - nsSoftwareUpdateIIDs.h \ - $(NULL) - -include $(topsrcdir)/config/rules.mk
deleted file mode 100644 --- a/xpinstall/public/nsIDOMInstallTriggerGlobal.h +++ /dev/null @@ -1,82 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef nsIDOMInstallTriggerGlobal_h__ -#define nsIDOMInstallTriggerGlobal_h__ - -#include "nsISupports.h" -#include "nsString.h" -#include "nsIScriptContext.h" -#include "nsXPITriggerInfo.h" -#include "nsIXPIInstallInfo.h" - - -#define NS_IDOMINSTALLTRIGGERGLOBAL_IID \ - { 0x23bb93a4, 0xdaee, 0x4a47, \ - {0x87, 0x76, 0xb1, 0x72, 0x35, 0x86, 0x2d, 0xac}} - -class nsIDOMInstallTriggerGlobal : public nsISupports { -public: - NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOMINSTALLTRIGGERGLOBAL_IID) - enum { - NOT_FOUND = -5, - MAJOR_DIFF = 4, - MINOR_DIFF = 3, - REL_DIFF = 2, - BLD_DIFF = 1, - EQUAL = 0 - }; - - NS_IMETHOD GetOriginatingURI(nsIScriptGlobalObject* aGlobalObject, nsIURI * *aUri)=0; - - NS_IMETHOD UpdateEnabled(nsIScriptGlobalObject* aGlobalObject, bool aUseWhitelist, bool* aReturn)=0; - - NS_IMETHOD UpdateEnabled(nsIURI* aURI, bool aUseWhitelist, bool* aReturn)=0; - - NS_IMETHOD StartInstall(nsIXPIInstallInfo* aInstallInfo, bool* aReturn)=0; - -}; - -NS_DEFINE_STATIC_IID_ACCESSOR(nsIDOMInstallTriggerGlobal, - NS_IDOMINSTALLTRIGGERGLOBAL_IID) - -extern nsresult NS_InitInstallTriggerGlobalClass(nsIScriptContext *aContext, void **aPrototype); - -extern "C" nsresult NS_NewScriptInstallTriggerGlobal(nsIScriptContext *aContext, nsISupports *aSupports, nsISupports *aParent, void **aReturn); - -#endif // nsIDOMInstallTriggerGlobal_h__
deleted file mode 100644 --- a/xpinstall/public/nsIXPIDialogService.idl +++ /dev/null @@ -1,107 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Mozilla XPInstall. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 2002 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsISupports.idl" - -interface nsIDOMWindow; -interface nsIXPIProgressDialog; -interface nsIObserver; - -/** - * A service provided by embedding applications to override - * the default XUL implmementation of XPInstall dialogs. - * - * Embedding applications which wish to override the default - * XUL dialogs need to create a component which implements - * this interface and registers with the Contract ID - * "@mozilla.org/embedui/xpinstall-dialog-service;1" - */ -[scriptable, uuid(8cdd8baa-1dd2-11b2-909a-f0178da5c5ff)] -interface nsIXPIDialogService : nsISupports -{ - /** - * @brief Ask the user if it's OK to install - * - * When called the XPIDialogService implementation should pose an - * install confirmation dialog and return the user's response - * - * @param parent a window that can be used to parent the modal dialog - * - * @param packageList For each install package there will be three strings, - * a display name, a source URL, and a the name of the - * organization that signed this install. Note that the - * name of the signer is not verified. Verification - * happens when the the install has completely downloaded. - * Your user interface should only suggest that the - * install may be signed by this organization name. - * Note that an unsigned archive is indicated by an - * empty string. - * - * @param count The number of strings in the packageList. This - * will always be three times the number of - * packages. - * - * @return true to install, false to cancel - */ - boolean confirmInstall(in nsIDOMWindow parent, - [array, size_is(count)] in wstring packageList, - in unsigned long count); - - /** - * @brief Create and open a download-and-install progress dialog - * - * When called the XPIDialogService implementation creates and opens - * a dialog to display the status of the install. When the dialog - * is ready to be used then the observer must be called: the subject - * is an nsIXPIProgressDialog that nsXPInstallManager can use to control - * the dialog, the topic is "xpinstall-progress" and the data is "open". - * - * If the user wishes to cancel the download, the dialog can call the - * observe method with the same subject and topic and the data "cancel". - * - * @note Unless this routine throws an exception the observer <b>must</b> - * be called or nsXPInstallManager will wait forever and never clean - * itself up. - * - * @param packageList three strings per package as in confirmInstall() - * @param count the number of strings in the list - * @param observer nsIObserver to receive messages from the dialog - */ - void openProgressDialog([array, size_is(count)] in wstring packageList, - in unsigned long count, - in nsIObserver observer); -};
deleted file mode 100644 --- a/xpinstall/public/nsIXPIInstallInfo.idl +++ /dev/null @@ -1,74 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla XPInstall. - * - * The Initial Developer of the Original Code is - * Dave Townsend <dtownsend@oxymoronical.com>. - * - * Portions created by the Initial Developer are Copyright (C) 2007 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** - */ - -#include "nsISupports.idl" - -[ptr] native triggerInfoPtr(nsXPITriggerInfo); - -interface nsIDOMWindow; -interface nsIDocShell; -interface nsIURI; - -/** - * Interface holding information about a triggered install that can be passed - * to and from script. - */ -[scriptable, uuid(5a4a775c-e452-4cf2-8ff8-d327ae24aec6)] -interface nsIXPIInstallInfo : nsISupports -{ - /** - * The install triggers supplied by the install. - */ - [noscript, notxpcom] attribute triggerInfoPtr triggerInfo; - - /** - * The original window that initiated the install. - */ - readonly attribute nsIDOMWindow originatingWindow; - - /** - * The original URI calling the install. This is the URI that would have been - * checked against the whitelist if necessary. - */ - readonly attribute nsIURI originatingURI; - - /** - * The chome type of the install. - */ - readonly attribute PRUint32 chromeType; -};
deleted file mode 100644 --- a/xpinstall/public/nsIXPIProgressDialog.idl +++ /dev/null @@ -1,80 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Mozilla XPInstall. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 2002 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsISupports.idl" - -/** - * Interface to display XPInstall download and install status. - */ -[scriptable, uuid(ce8f744e-d5a5-41b3-911f-0fee3008b64e)] -interface nsIXPIProgressDialog : nsISupports -{ - /** - * state values for onStateChange - */ - const short DOWNLOAD_START = 0; - const short DOWNLOAD_DONE = 1; - const short INSTALL_START = 2; - const short INSTALL_DONE = 3; - const short DIALOG_CLOSE = 4; - - /** - * basic info to control the install progress dialog. The dialog can - * go away any time after it has received the DIALOG_CLOSE state message - * but needs to accept messages until that time even if it is not visible. - * - * Normally for each install package the dialog will receive the download - * and install messages in START/DONE pairs, but in the case of a download - * error the dialog will be sent only the DOWNLOAD_START followed by an - * INSTALL_DONE message with the value nsInstall::DOWNLOAD_ERROR - * - * @param index the package this message is about of those passed into - * openProgressDialog. ignored when state==DIALOG_CLOSE - * @param state the kind of message - * @param value final result when state==INSTALL_DONE, otherwise ignored - */ - void onStateChange( in unsigned long index, in short state, in long value ); - - /** - * download progress - * - * @param index the package to which this refers - * @param value number of bytes downloaded - * @param maxValue the total size - */ - void onProgress( in unsigned long index, in unsigned long long value, in unsigned long long maxValue ); -};
deleted file mode 100644 --- a/xpinstall/public/nsIXPInstallManager.idl +++ /dev/null @@ -1,84 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Mozilla XPInstall. - * - * The Initial Developer of the Original Code is Ben Goodger. - * Portions created by the Initial Developer are Copyright (C) 2004 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Ben Goodger <ben@mozilla.org> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsISupports.idl" - -interface nsIXPIProgressDialog; -interface nsIXPIInstallInfo; - -/** - * Interface to XPInstallManager - manages download and install operations. - */ -[scriptable, uuid(83fdd52f-2d34-4e22-981d-cf3c4ae76faa)] -interface nsIXPInstallManager : nsISupports -{ - /** - * Initiates a download and install operation of the supplied URLs - * and sends notifications to the supplied listener. - * @param aURLs array of XPI urls to download and install - * @param aURLCount number of XPI urls in aURLs - * @param aListener a listener to receive status notifications - */ - void initManagerFromChrome([array, size_is(aURLCount)] in wstring aURLs, - in unsigned long aURLCount, - in nsIXPIProgressDialog aListener); - /** - * Initiates a set of downloads and checks the supplied hashes after - * download. Just like initManagerFromChrome() in all other respects - * @param aURLs array of XPI urls to download and install - * @param aHashes array of hash strings to validate. The entire array - * or individual hashes can be null to indicate no - * checking. If supplied looks like "type:hash", like - * "md5:3232bc5624041c507db0965324188024". - * Supports the types in nsICryptoHash - * @param aURLCount number of XPI urls in aURLs and aHashes - * @param aListener a listener to receive status notifications - */ - void initManagerWithHashes([array, size_is(aURLCount)] in wstring aURLs, - [array, size_is(aURLCount)] in string aHashes, - in unsigned long aURLCount, - in nsIXPIProgressDialog aListener); - - /** - * Initiates a set of downloads based on an install info object. Will - * display confirmation dialog as if the install info had been supplied - * by content. - * @param aInstallInfo The install info object providing install triggers - * and script context for the install. - */ - void initManagerWithInstallInfo(in nsIXPIInstallInfo aInstallInfo); -}; -
deleted file mode 100644 --- a/xpinstall/public/nsPICertNotification.idl +++ /dev/null @@ -1,48 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is XPInstall Signing. - * - * The Initial Developer of the Original Code is Doug Turner. - * Portions created by the Initial Developer are Copyright (C) 2002 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsISupports.idl" - -interface nsIURI; -interface nsIPrincipal; - -[uuid(42cd7162-ea4a-4088-9888-63ea5095869e)] -interface nsPICertNotification : nsISupports -{ - void onCertAvailable(in nsIURI aURI, - in nsISupports aContext, - in PRUint32 aStatus, - in nsIPrincipal aPrincipal); -};
deleted file mode 100644 --- a/xpinstall/public/nsSoftwareUpdateIIDs.h +++ /dev/null @@ -1,62 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> - * Douglas Turner <dougt@netscape.com> - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef nsSoftwareUpdateIIDs_h___ -#define nsSoftwareUpdateIIDs_h___ - -#define NS_SoftwareUpdateInstallTrigger_CID \ -{ /* 18c2f98d-b09f-11d2-bcde-00805f0e1353 */ \ - 0x18c2f98d, \ - 0xb09f, \ - 0x11d2, \ - {0xbc, 0xde, 0x00, 0x80, 0x5f, 0x0e, 0x13, 0x53} \ -} - -#define NS_XPInstallManager_CID \ -{ /* {6a4d4c1e-a74a-4320-8124-16233a0183d6} */ \ - 0x6a4d4c1e, \ - 0xa74a, \ - 0x4320, \ - { 0x81, 0x24, 0x16, 0x23, 0x3a, 0x1, 0x83, 0xd6} \ -} - - -#endif /* nsSoftwareUpdateIIDs_h___ */ -
deleted file mode 100644 --- a/xpinstall/public/xpinstall.js +++ /dev/null @@ -1,2 +0,0 @@ -pref("xpinstall.enabled", true); -pref("xpinstall.whitelist.required", true);
deleted file mode 100644 --- a/xpinstall/src/CertReader.cpp +++ /dev/null @@ -1,279 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is XPInstall Signing. - * - * The Initial Developer of the Original Code is Doug Turner. - * Portions created by the Initial Developer are Copyright (C) 2002 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "zlib.h" -#include "zipstruct.h" - -#include "CertReader.h" - -#include "nsCRT.h" -#include "nsIServiceManager.h" -#include "nsISignatureVerifier.h" -#include "nsIInputStream.h" -#include "nsIPrincipal.h" -#include "nsIURI.h" -#include "nsPICertNotification.h" - - -#include "nsNetUtil.h" - -// just a guess at the max size of the cert. -#define MAX_SIGNATURE_SIZE (32*1024) - - -/* - * x t o i n t - * - * Converts a two byte ugly endianed integer - * to our platform's integer. - * - */ - -static unsigned int xtoint (unsigned char *ii) -{ - return (int) (ii [0]) | ((int) ii [1] << 8); -} - -/* - * x t o l o n g - * - * Converts a four byte ugly endianed integer - * to our platform's integer. - * - */ - -static unsigned long xtolong (unsigned char *ll) -{ - unsigned long ret; - - ret = ((((unsigned long) ll [0]) << 0) | - (((unsigned long) ll [1]) << 8) | - (((unsigned long) ll [2]) << 16) | - (((unsigned long) ll [3]) << 24) ); - - return ret; -} - -static int my_inflate(unsigned char* compr, PRUint32 comprLen, unsigned char* uncompr, PRUint32 uncomprLen) -{ - int err; - z_stream d_stream; /* decompression stream */ - memset (&d_stream, 0, sizeof (d_stream)); - - // buffer is way to small to even deal with. - if (uncomprLen < 10) - return -1; - - *uncompr = '\0'; - - if (inflateInit2 (&d_stream, -MAX_WBITS) != Z_OK) - return -1; - - d_stream.next_in = compr; - d_stream.avail_in = (uInt)comprLen; - - d_stream.next_out = uncompr; - d_stream.avail_out = (uInt)uncomprLen; - - err = inflate(&d_stream, Z_NO_FLUSH); - - if (err != Z_OK && err != Z_STREAM_END) { - inflateEnd(&d_stream); - return -1; - } - - err = inflateEnd(&d_stream); - if (err != Z_OK) { - return -1; - } - return 0; -} - -CertReader::CertReader(nsIURI* aURI, nsISupports* aContext, nsPICertNotification* aObs): - mContext(aContext), - mURI(aURI), - mObserver(aObs) -{ -} - -CertReader::~CertReader() -{ -} - -NS_IMPL_ISUPPORTS2(CertReader, nsIStreamListener, nsIRequestObserver) - -NS_IMETHODIMP -CertReader::OnStartRequest(nsIRequest *request, nsISupports* context) -{ - mVerifier = do_GetService(SIGNATURE_VERIFIER_CONTRACTID); - if (!mVerifier) - return NS_BINDING_ABORTED; - - nsCOMPtr<nsILoadGroup> loadGroup; - nsresult rv = request->GetLoadGroup(getter_AddRefs(loadGroup)); - if (NS_SUCCEEDED(rv) && loadGroup) - loadGroup->RemoveRequest(request, nsnull, NS_BINDING_RETARGETED); - - mLeftoverBuffer.Truncate(); - return NS_OK; -} - -NS_IMETHODIMP -CertReader::OnDataAvailable(nsIRequest *request, - nsISupports* context, - nsIInputStream *aIStream, - PRUint32 aSourceOffset, - PRUint32 aLength) -{ - if (!mVerifier) - return NS_BINDING_ABORTED; - - char buf[4096]; - PRUint32 amt, size; - nsresult rv; - - while (aLength) - { - size = NS_MIN(aLength, sizeof(buf)); - - rv = aIStream->Read(buf, size, &amt); - if (NS_FAILED(rv)) - return rv; - - aLength -= amt; - - mLeftoverBuffer.Append(buf, amt); - - if (mLeftoverBuffer.Length() < ZIPLOCAL_SIZE) - continue; - - const char* caret = mLeftoverBuffer.get(); - - ZipLocal_* ziplocal = (ZipLocal_*) caret; - - if (xtolong(ziplocal->signature) != LOCALSIG) - return NS_BINDING_ABORTED; - - // did we read the entire file entry into memory? - PRUint32 fileEntryLen = (ZIPLOCAL_SIZE + - xtoint(ziplocal->filename_len) + - xtoint(ziplocal->extrafield_len) + - xtolong(ziplocal->size)); - - - // prevent downloading a huge file on an unsigned cert - if (fileEntryLen > MAX_SIGNATURE_SIZE) - return NS_BINDING_ABORTED; - - if (mLeftoverBuffer.Length() < fileEntryLen) - { - // we are just going to buffer and continue. - continue; - } - - // the assumption here is that we have the fileEntry in mLeftoverBuffer - - int err = 0; - unsigned char* orgData = nsnull; - unsigned char* sigData = nsnull; - const char* data = (caret + - ZIPLOCAL_SIZE + - xtoint(ziplocal->filename_len) + - xtoint(ziplocal->extrafield_len)); - - PRUint32 sigSize = 0; - PRUint32 orgSize = xtolong ((unsigned char *) ziplocal->orglen); - PRUint32 cSize = xtolong ((unsigned char *) ziplocal->size); - - switch (xtoint(ziplocal->method)) - { - case STORED: - // file is uncompressed, can use the data where it is - sigSize = cSize; - sigData = (unsigned char*)data; - break; - - case DEFLATED: - if (orgSize == 0 || orgSize > MAX_SIGNATURE_SIZE) - return NS_BINDING_ABORTED; - - orgData = (unsigned char*)malloc(orgSize); - if (!orgData) - return NS_BINDING_ABORTED; - - err = my_inflate((unsigned char*)data, - cSize, - orgData, - orgSize); - - sigSize = orgSize; - sigData = orgData; - break; - - default: - // unsupported compression method - err = Z_DATA_ERROR; - break; - } - - if (err == 0) - { - PRInt32 verifyError; - rv = mVerifier->VerifySignature((char*)sigData, sigSize, nsnull, 0, - &verifyError, getter_AddRefs(mPrincipal)); - } - if (orgData) - free(orgData); - - // Cancel the load now that we've verified the signature - return NS_BINDING_ABORTED; - } - - return NS_OK; // continue reading -} - -NS_IMETHODIMP -CertReader::OnStopRequest(nsIRequest *request, nsISupports* context, - nsresult aStatus) -{ - mObserver->OnCertAvailable(mURI, - mContext, - aStatus, - mPrincipal); - - return NS_OK; -} - -
deleted file mode 100644 --- a/xpinstall/src/CertReader.h +++ /dev/null @@ -1,64 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is XPInstall Signing. - * - * The Initial Developer of the Original Code is Doug Turner. - * Portions created by the Initial Developer are Copyright (C) 2002 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsCOMPtr.h" -#include "nsIStreamListener.h" -#include "nsString.h" - -class nsISignatureVerifier; -class nsIPrincipal; -class nsIURI; -class nsPICertNotification; - - -class CertReader : public nsIStreamListener -{ -public: - CertReader(nsIURI* uri, nsISupports* aContext, nsPICertNotification* aObs); - virtual ~CertReader(); - - NS_DECL_ISUPPORTS - NS_DECL_NSIREQUESTOBSERVER - NS_DECL_NSISTREAMLISTENER - -private: - nsCString mLeftoverBuffer; - nsCOMPtr<nsIPrincipal> mPrincipal; - nsCOMPtr<nsISignatureVerifier> mVerifier; - - nsCOMPtr<nsISupports> mContext; - nsCOMPtr<nsIURI> mURI; - nsCOMPtr<nsPICertNotification> mObserver; -};
deleted file mode 100644 --- a/xpinstall/src/Makefile.in +++ /dev/null @@ -1,74 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is Mozilla Communicator client code, released -# March 31, 1998. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Daniel Veditz <dveditz@netscape.com> -# Douglas Turner <dougt@netscape.com> -# Dave Townsend <dtownsend@oxymoronical.com> -# -# Alternatively, the contents of this file may be used under the terms of -# either of the GNU General Public License Version 2 or later (the "GPL"), -# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** - -DEPTH = ../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -MODULE = xpinstall -LIBRARY_NAME = xpinstall - -EXPORT_LIBRARY = 1 -IS_COMPONENT = 1 -MODULE_NAME = nsSoftwareUpdate -GRE_MODULE = 1 -LIBXUL_LIBRARY = 1 - - -# XXX shouldn't need to export this -EXPORTS = nsXPITriggerInfo.h - -CPPSRCS = \ - CertReader.cpp \ - nsInstallTrigger.cpp \ - nsJSInstallTriggerGlobal.cpp \ - nsSoftwareUpdate.cpp \ - nsXPITriggerInfo.cpp \ - nsXPInstallManager.cpp \ - nsXPIInstallInfo.cpp \ - $(NULL) - -LOCAL_INCLUDES = -I$(srcdir)/../public - -include $(topsrcdir)/config/rules.mk
deleted file mode 100644 --- a/xpinstall/src/nsInstall.h +++ /dev/null @@ -1,117 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> - * Douglas Turner <dougt@netscape.com> - * Jens Bannmann <jens.b@web.de> - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef __NS_INSTALL_H__ -#define __NS_INSTALL_H__ - -class nsInstall -{ - public: - - enum - { - BAD_PACKAGE_NAME = -200, - UNEXPECTED_ERROR = -201, - ACCESS_DENIED = -202, - EXECUTION_ERROR = -203, - NO_INSTALL_SCRIPT = -204, - NO_CERTIFICATE = -205, - NO_MATCHING_CERTIFICATE = -206, - CANT_READ_ARCHIVE = -207, - INVALID_ARGUMENTS = -208, - ILLEGAL_RELATIVE_PATH = -209, - USER_CANCELLED = -210, - INSTALL_NOT_STARTED = -211, - SILENT_MODE_DENIED = -212, - NO_SUCH_COMPONENT = -213, - DOES_NOT_EXIST = -214, - READ_ONLY = -215, - IS_DIRECTORY = -216, - NETWORK_FILE_IS_IN_USE = -217, - APPLE_SINGLE_ERR = -218, - INVALID_PATH_ERR = -219, - PATCH_BAD_DIFF = -220, - PATCH_BAD_CHECKSUM_TARGET = -221, - PATCH_BAD_CHECKSUM_RESULT = -222, - UNINSTALL_FAILED = -223, - PACKAGE_FOLDER_NOT_SET = -224, - EXTRACTION_FAILED = -225, - FILENAME_ALREADY_USED = -226, - INSTALL_CANCELLED = -227, - DOWNLOAD_ERROR = -228, - SCRIPT_ERROR = -229, - - ALREADY_EXISTS = -230, - IS_FILE = -231, - SOURCE_DOES_NOT_EXIST = -232, - SOURCE_IS_DIRECTORY = -233, - SOURCE_IS_FILE = -234, - INSUFFICIENT_DISK_SPACE = -235, - FILENAME_TOO_LONG = -236, - - UNABLE_TO_LOCATE_LIB_FUNCTION = -237, - UNABLE_TO_LOAD_LIBRARY = -238, - - CHROME_REGISTRY_ERROR = -239, - - MALFORMED_INSTALL = -240, - - KEY_ACCESS_DENIED = -241, - KEY_DOES_NOT_EXIST = -242, - VALUE_DOES_NOT_EXIST = -243, - - UNSUPPORTED_TYPE = -244, - - INVALID_SIGNATURE = -260, - INVALID_HASH = -261, - INVALID_HASH_TYPE = -262, - - OUT_OF_MEMORY = -299, - - GESTALT_UNKNOWN_ERR = -5550, - GESTALT_INVALID_ARGUMENT = -5551, - - SUCCESS = 0, - REBOOT_NEEDED = 999 - }; -}; - -#endif
deleted file mode 100644 --- a/xpinstall/src/nsInstallTrigger.cpp +++ /dev/null @@ -1,453 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - - -#include "nsXPInstallManager.h" -#include "nsInstallTrigger.h" -#include "nsIDOMInstallTriggerGlobal.h" - -#include "nscore.h" -#include "nsAutoPtr.h" -#include "netCore.h" -#include "nsIFactory.h" -#include "nsISupports.h" -#include "nsPIDOMWindow.h" -#include "nsIScriptGlobalObject.h" -#include "nsIScriptGlobalObjectOwner.h" - -#include "nsIPrefBranch.h" -#include "nsIPrefService.h" -#include "nsIPermissionManager.h" -#include "nsIDocShell.h" -#include "nsNetUtil.h" -#include "nsIDOMDocument.h" -#include "nsIDocument.h" -#include "nsIPrincipal.h" -#include "nsIObserverService.h" -#include "nsIPropertyBag2.h" - -#include "nsIComponentManager.h" -#include "nsIServiceManager.h" - -#include "nsIContentHandler.h" -#include "nsIChannel.h" -#include "nsIURI.h" -#include "nsXPIInstallInfo.h" - - -nsInstallTrigger::nsInstallTrigger() -{ - mScriptObject = nsnull; -} - -nsInstallTrigger::~nsInstallTrigger() -{ -} - - -NS_IMPL_THREADSAFE_ISUPPORTS3 (nsInstallTrigger, - nsIScriptObjectOwner, - nsIDOMInstallTriggerGlobal, - nsIContentHandler) - - -NS_IMETHODIMP -nsInstallTrigger::GetScriptObject(nsIScriptContext *aContext, void** aScriptObject) -{ - NS_PRECONDITION(nsnull != aScriptObject, "null arg"); - nsresult res = NS_OK; - - if (nsnull == mScriptObject) - { - res = NS_NewScriptInstallTriggerGlobal(aContext, - (nsIDOMInstallTriggerGlobal*)this, - aContext->GetGlobalObject(), - &mScriptObject); - } - - *aScriptObject = mScriptObject; - return res; -} - -NS_IMETHODIMP -nsInstallTrigger::SetScriptObject(void *aScriptObject) -{ - mScriptObject = aScriptObject; - return NS_OK; -} - - - - -NS_IMETHODIMP -nsInstallTrigger::HandleContent(const char * aContentType, - nsIInterfaceRequestor* aWindowContext, - nsIRequest* aRequest) -{ - nsresult rv = NS_OK; - if (!aRequest) - return NS_ERROR_NULL_POINTER; - - if (nsCRT::strcasecmp(aContentType, "application/x-xpinstall") != 0) - { - // We only support content-type application/x-xpinstall - return NS_ERROR_WONT_HANDLE_CONTENT; - } - - // Save the URI so nsXPInstallManager can re-load it later - nsCOMPtr<nsIURI> uri; - nsCAutoString urispec; - nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); - if (channel) - { - rv = channel->GetURI(getter_AddRefs(uri)); - if (NS_SUCCEEDED(rv) && uri) - rv = uri->GetSpec(urispec); - } - if (NS_FAILED(rv)) - return rv; - if (urispec.IsEmpty()) - return NS_ERROR_ILLEGAL_VALUE; - - - // Save the referrer if any, for permission checks - NS_NAMED_LITERAL_STRING(referrerProperty, "docshell.internalReferrer"); - bool useReferrer = false; - nsCOMPtr<nsIURI> referringURI; - nsCOMPtr<nsIPropertyBag2> channelprops(do_QueryInterface(channel)); - - if (channelprops) - { - // Get the referrer from the channel properties if we can (not all - // channels support our internal-referrer property). - // - // It's possible docshell explicitly set a null referrer in the case - // of typed, pasted, or bookmarked URLs and the like. In such a case - // we get a success return value with null pointer. - // - // A null referrer is automatically whitelisted as an explicit user - // action (though they'll still get the confirmation dialog). For a - // missing referrer we go to our fall-back plan of using the XPI - // location for whitelisting purposes. - rv = channelprops->GetPropertyAsInterface(referrerProperty, - NS_GET_IID(nsIURI), - getter_AddRefs(referringURI)); - if (NS_SUCCEEDED(rv)) - useReferrer = true; - } - - // Cancel the current request. nsXPInstallManager restarts the download - // under its control (shared codepath with InstallTrigger) - aRequest->Cancel(NS_BINDING_ABORTED); - - - // Get the global object of the target window for StartSoftwareUpdate - nsCOMPtr<nsIScriptGlobalObjectOwner> globalObjectOwner = - do_QueryInterface(aWindowContext); - nsIScriptGlobalObject* globalObject = - globalObjectOwner ? globalObjectOwner->GetScriptGlobalObject() : nsnull; - if ( !globalObject ) - return NS_ERROR_INVALID_ARG; - - - nsCOMPtr<nsIURI> checkuri; - - if ( useReferrer ) - { - // easiest and most common case: base decision on the page that - // contained the link - // - // NOTE: the XPI itself may be from elsewhere; the user can decide if - // they trust the actual source when they get the install confirmation - // dialog. The decision we're making here is whether the triggering - // site is one which is allowed to annoy the user with modal dialogs. - - checkuri = referringURI; - } - else - { - // Now we're stumbing in the dark. In the most likely case the user - // simply clicked on an FTP link (no referrer) and it's perfectly - // sane to use the current window. - // - // On the other hand the user might be opening a non-http XPI link - // in an unrelated existing window (typed in location bar, bookmark, - // dragged link ...) in which case the current window is irrelevant. - // If we knew it was one of these explicit user actions we'd like to - // allow it, but we have no way of knowing that here. - // - // But there's no way to distinguish the innocent cases from a clever - // malicious site. If we used the target window then evil.com could - // embed a presumed allowed site (e.g. mozilla.org) in a frame, then - // change the location to the XPI and trigger the install. Or evil.com - // could do the same thing in a new window (more work to get around - // popup blocking, but possible). - // - // Our choices appear to be block this type of load entirely or to - // trust only the install URI. The former is unacceptably restrictive, - // the latter allows malicious sites to pester people with modal - // dialogs. As long as the trusted sites don't host bad content that's - // no worse than an endless stream of alert()s -- already possible. - // If the trusted sites don't even have an ftp server then even this - // level of annoyance is not possible. - // - // If a trusted site hosts an install with an exploitable flaw it - // might be possible that a malicious site would attempt to trick - // people into installing it, hoping to turn around and exploit it. - // This is not entirely far-fetched (it's been done with ActiveX - // controls) and will require community policing of the default - // trusted sites. - - checkuri = uri; - } - - nsAutoPtr<nsXPITriggerInfo> trigger(new nsXPITriggerInfo()); - nsAutoPtr<nsXPITriggerItem> item(new nsXPITriggerItem(0, NS_ConvertUTF8toUTF16(urispec).get(), - nsnull)); - if (trigger && item) - { - // trigger will own the item now - trigger->Add(item.forget()); - nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(globalObject); - nsCOMPtr<nsIXPIInstallInfo> installInfo = - new nsXPIInstallInfo(win, checkuri, trigger, 0); - if (installInfo) - { - // From here trigger is owned by installInfo until passed on to nsXPInstallManager - trigger.forget(); - if (AllowInstall(checkuri)) - { - return StartInstall(installInfo, nsnull); - } - else - { - nsCOMPtr<nsIObserverService> os = - mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(installInfo, - "xpinstall-install-blocked", - nsnull); - return NS_ERROR_ABORT; - } - } - } - return NS_ERROR_OUT_OF_MEMORY; -} - - -// updateWhitelist -// -// Helper function called by nsInstallTrigger::AllowInstall(). -// Interprets the pref as a comma-delimited list of hosts and adds each one -// to the permission manager using the given action. Clear pref when done. -static void updatePermissions( const char* aPref, - PRUint32 aPermission, - nsIPermissionManager* aPermissionManager, - nsIPrefBranch* aPrefBranch) -{ - NS_PRECONDITION(aPref && aPermissionManager && aPrefBranch, "Null arguments!"); - - nsXPIDLCString hostlist; - nsresult rv = aPrefBranch->GetCharPref( aPref, getter_Copies(hostlist)); - if (NS_SUCCEEDED(rv) && !hostlist.IsEmpty()) - { - nsCAutoString host; - PRInt32 start=0, match=0; - nsresult rv; - nsCOMPtr<nsIURI> uri; - - do { - match = hostlist.FindChar(',', start); - - host = Substring(hostlist, start, match-start); - host.CompressWhitespace(); - host.Insert("http://", 0); - - rv = NS_NewURI(getter_AddRefs(uri), host); - if (NS_SUCCEEDED(rv)) - { - aPermissionManager->Add( uri, XPI_PERMISSION, aPermission, - nsIPermissionManager::EXPIRE_NEVER, 0 ); - } - start = match+1; - } while ( match > 0 ); - - // save empty list, we don't need to do this again - aPrefBranch->SetCharPref( aPref, ""); - } -} - - -// Check whether an Install is allowed. The launching URI can be null, -// in which case only the global pref-setting matters. -bool -nsInstallTrigger::AllowInstall(nsIURI* aLaunchURI) -{ - // Check the global setting. - bool xpiEnabled = false; - nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); - if ( !prefBranch) - { - return true; // no pref service in native install, it's OK - } - - prefBranch->GetBoolPref( XPINSTALL_ENABLE_PREF, &xpiEnabled); - if ( !xpiEnabled ) - { - // globally turned off - return false; - } - - - // Check permissions for the launching host if we have one - nsCOMPtr<nsIPermissionManager> permissionMgr = - do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); - - if ( permissionMgr && aLaunchURI ) - { - bool isChrome = false; - bool isFile = false; - aLaunchURI->SchemeIs( "chrome", &isChrome ); - aLaunchURI->SchemeIs( "file", &isFile ); - - // file: and chrome: don't need whitelisted hosts - if ( !isChrome && !isFile ) - { - // check prefs for permission updates before testing URI - updatePermissions( XPINSTALL_WHITELIST_ADD, - nsIPermissionManager::ALLOW_ACTION, - permissionMgr, prefBranch ); - updatePermissions( XPINSTALL_WHITELIST_ADD_36, - nsIPermissionManager::ALLOW_ACTION, - permissionMgr, prefBranch ); - updatePermissions( XPINSTALL_BLACKLIST_ADD, - nsIPermissionManager::DENY_ACTION, - permissionMgr, prefBranch ); - - bool requireWhitelist = true; - prefBranch->GetBoolPref( XPINSTALL_WHITELIST_REQUIRED, &requireWhitelist ); - - PRUint32 permission = nsIPermissionManager::UNKNOWN_ACTION; - permissionMgr->TestPermission( aLaunchURI, XPI_PERMISSION, &permission ); - - if ( permission == nsIPermissionManager::DENY_ACTION ) - { - xpiEnabled = false; - } - else if ( requireWhitelist && - permission != nsIPermissionManager::ALLOW_ACTION ) - { - xpiEnabled = false; - } - } - } - - return xpiEnabled; -} - - -NS_IMETHODIMP -nsInstallTrigger::GetOriginatingURI(nsIScriptGlobalObject* aGlobalObject, nsIURI * *aUri) -{ - NS_ENSURE_ARG_POINTER(aGlobalObject); - - *aUri = nsnull; - - // find the current site - nsCOMPtr<nsIDOMDocument> domdoc; - nsCOMPtr<nsIDOMWindow> window(do_QueryInterface(aGlobalObject)); - if ( window ) - { - window->GetDocument(getter_AddRefs(domdoc)); - nsCOMPtr<nsIDocument> doc(do_QueryInterface(domdoc)); - if ( doc ) - NS_ADDREF(*aUri = doc->GetDocumentURI()); - } - return NS_OK; -} - -NS_IMETHODIMP -nsInstallTrigger::UpdateEnabled(nsIScriptGlobalObject* aGlobalObject, bool aUseWhitelist, bool* aReturn) -{ - nsCOMPtr<nsIURI> uri; - nsresult rv = GetOriginatingURI(aGlobalObject, getter_AddRefs(uri)); - NS_ENSURE_SUCCESS(rv, rv); - return UpdateEnabled(uri, aUseWhitelist, aReturn); -} - -NS_IMETHODIMP -nsInstallTrigger::UpdateEnabled(nsIURI* aURI, bool aUseWhitelist, bool* aReturn) -{ - // disallow unless we successfully find otherwise - *aReturn = false; - - if (!aUseWhitelist) - { - // simple global pref check - nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); - if (prefBranch) - prefBranch->GetBoolPref( XPINSTALL_ENABLE_PREF, aReturn); - } - else if (aURI) - { - *aReturn = AllowInstall(aURI); - } - - return NS_OK; -} - - -NS_IMETHODIMP -nsInstallTrigger::StartInstall(nsIXPIInstallInfo* aInstallInfo, bool* aReturn) -{ - if (aReturn) - *aReturn = false; - - nsXPInstallManager *mgr = new nsXPInstallManager(); - if (mgr) - { - nsresult rv = mgr->InitManagerWithInstallInfo(aInstallInfo); - if (NS_SUCCEEDED(rv) && aReturn) - *aReturn = true; - return rv; - } - else - { - return NS_ERROR_OUT_OF_MEMORY; - } -}
deleted file mode 100644 --- a/xpinstall/src/nsInstallTrigger.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef __NS_INSTALLTRIGGER_H__ -#define __NS_INSTALLTRIGGER_H__ - -#include "nscore.h" -#include "nsString.h" -#include "nsIFactory.h" -#include "nsISupports.h" -#include "nsIScriptObjectOwner.h" - -#include "prtypes.h" -#include "nsHashtable.h" - -#include "nsIDOMInstallTriggerGlobal.h" -#include "nsXPITriggerInfo.h" - -#include "nsIContentHandler.h" - -#define NOT_CHROME 0 -#define CHROME_SKIN 1 -#define CHROME_LOCALE 2 -#define CHROME_SAFEMAX CHROME_SKIN -#define CHROME_CONTENT 4 -#define CHROME_ALL (CHROME_SKIN | CHROME_LOCALE | CHROME_CONTENT) -#define CHROME_PROFILE 8 -#define CHROME_DELAYED 0x10 -#define CHROME_SELECT 0x20 - -#define XPI_PERMISSION "install" - -#define XPI_WHITELIST true -#define XPI_GLOBAL false - -#define XPINSTALL_ENABLE_PREF "xpinstall.enabled" -#define XPINSTALL_WHITELIST_ADD "xpinstall.whitelist.add" -#define XPINSTALL_WHITELIST_ADD_36 "xpinstall.whitelist.add.36" -#define XPINSTALL_WHITELIST_REQUIRED "xpinstall.whitelist.required" -#define XPINSTALL_BLACKLIST_ADD "xpinstall.blacklist.add" - -class nsInstallTrigger: public nsIScriptObjectOwner, - public nsIDOMInstallTriggerGlobal, - public nsIContentHandler -{ - public: - nsInstallTrigger(); - virtual ~nsInstallTrigger(); - - NS_DECL_ISUPPORTS - NS_DECL_NSICONTENTHANDLER - - NS_IMETHOD GetScriptObject(nsIScriptContext *aContext, void** aScriptObject); - NS_IMETHOD SetScriptObject(void* aScriptObject); - - NS_IMETHOD GetOriginatingURI(nsIScriptGlobalObject* aGlobalObject, nsIURI * *aUri); - NS_IMETHOD UpdateEnabled(nsIScriptGlobalObject* aGlobalObject, bool aUseWhitelist, bool* aReturn); - NS_IMETHOD UpdateEnabled(nsIURI* aURI, bool aUseWhitelist, bool* aReturn); - NS_IMETHOD StartInstall(nsIXPIInstallInfo* aInstallInfo, bool* aReturn); - - - private: - bool AllowInstall(nsIURI* aLaunchURI); - void *mScriptObject; -}; - -#define NS_INSTALLTRIGGERCOMPONENT_CONTRACTID "@mozilla.org/xpinstall/installtrigger;1" -#endif
deleted file mode 100644 --- a/xpinstall/src/nsJSInstallTriggerGlobal.cpp +++ /dev/null @@ -1,834 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "jsapi.h" -#include "nscore.h" -#include "nsAutoPtr.h" -#include "nsIScriptContext.h" -#include "nsIScriptObjectOwner.h" -#include "nsIScriptGlobalObject.h" -#include "nsCRT.h" -#include "nsString.h" -#include "nsIDOMInstallTriggerGlobal.h" -#include "nsPIDOMWindow.h" -#include "nsIDOMDocument.h" -#include "nsIDocument.h" -#include "nsIDocShell.h" -#include "nsIObserverService.h" -#include "nsInstallTrigger.h" -#include "nsXPITriggerInfo.h" -#include "nsDOMJSUtils.h" -#include "nsXPIInstallInfo.h" - -#include "nsIComponentManager.h" -#include "nsNetUtil.h" -#include "nsIScriptSecurityManager.h" - -#include "nsSoftwareUpdateIIDs.h" - -void ConvertJSValToStr(nsString& aString, - JSContext* aContext, - jsval aValue) -{ - JSString *jsstring; - - if ( !JSVAL_IS_NULL(aValue) && - (jsstring = JS_ValueToString(aContext, aValue)) != nsnull) - { - aString.Assign(reinterpret_cast<const PRUnichar*>(JS_GetStringChars(jsstring))); - } - else - { - aString.Truncate(); - } -} - -static void -FinalizeInstallTriggerGlobal(JSContext *cx, JSObject *obj); - -/***********************************************************************/ -// -// class for InstallTriggerGlobal -// -JSClass InstallTriggerGlobalClass = { - "InstallTrigger", - JSCLASS_HAS_PRIVATE, - JS_PropertyStub, - JS_PropertyStub, - JS_PropertyStub, - JS_PropertyStub, - JS_EnumerateStub, - JS_ResolveStub, - JS_ConvertStub, - FinalizeInstallTriggerGlobal -}; - - -// -// InstallTriggerGlobal finalizer -// -static void -FinalizeInstallTriggerGlobal(JSContext *cx, JSObject *obj) -{ - nsISupports *nativeThis = (nsISupports*)JS_GetPrivate(cx, obj); - - if (nsnull != nativeThis) { - // get the js object - nsIScriptObjectOwner *owner = nsnull; - if (NS_OK == nativeThis->QueryInterface(NS_GET_IID(nsIScriptObjectOwner), - (void**)&owner)) { - owner->SetScriptObject(nsnull); - NS_RELEASE(owner); - } - - // The addref was part of JSObject construction - NS_RELEASE(nativeThis); - } -} - -static JSBool CreateNativeObject(JSContext *cx, JSObject *obj, nsIDOMInstallTriggerGlobal **aResult) -{ - nsresult result; - nsIScriptObjectOwner *owner = nsnull; - nsIDOMInstallTriggerGlobal *nativeThis; - - static NS_DEFINE_CID(kInstallTrigger_CID, - NS_SoftwareUpdateInstallTrigger_CID); - - result = CallCreateInstance(kInstallTrigger_CID, &nativeThis); - if (NS_FAILED(result)) return JS_FALSE; - - result = nativeThis->QueryInterface(NS_GET_IID(nsIScriptObjectOwner), - (void **)&owner); - - if (NS_OK != result) - { - NS_RELEASE(nativeThis); - return JS_FALSE; - } - - owner->SetScriptObject((void *)obj); - JS_SetPrivate(cx, obj, nativeThis); - - *aResult = nativeThis; - - NS_RELEASE(nativeThis); // we only want one refcnt. JSUtils cleans us up. - return JS_TRUE; -} - -// -// Helper function for URI verification -// -static nsresult -InstallTriggerCheckLoadURIFromScript(JSContext *cx, const nsAString& uriStr) -{ - nsresult rv; - nsCOMPtr<nsIScriptSecurityManager> secman( - do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID,&rv)); - NS_ENSURE_SUCCESS(rv, rv); - - // get the script principal - nsCOMPtr<nsIPrincipal> principal; - rv = secman->GetSubjectPrincipal(getter_AddRefs(principal)); - NS_ENSURE_SUCCESS(rv, rv); - if (!principal) - return NS_ERROR_FAILURE; - - // convert the requested URL string to a URI - // Note that we use a null base URI here, since that's what we use when we - // actually convert the string into a URI to load. - nsCOMPtr<nsIURI> uri; - rv = NS_NewURI(getter_AddRefs(uri), uriStr); - NS_ENSURE_SUCCESS(rv, rv); - - // are we allowed to load this one? - rv = secman->CheckLoadURIWithPrincipal(principal, uri, - nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL); - return rv; -} - -// -// Helper function to get native object -// -// This is our own version of JS_GetInstancePrivate() that in addition -// performs the delayed creation of the native InstallTrigger if necessary -// -static nsIDOMInstallTriggerGlobal* getTriggerNative(JSContext *cx, JSObject *obj) -{ - if (!JS_InstanceOf(cx, obj, &InstallTriggerGlobalClass, nsnull)) - return nsnull; - - nsIDOMInstallTriggerGlobal *native = (nsIDOMInstallTriggerGlobal*)JS_GetPrivate(cx, obj); - if (!native) { - // xpinstall script contexts delay creation of the native. - CreateNativeObject(cx, obj, &native); - } - return native; -} - -// -// Native method UpdateEnabled -// -static JSBool -InstallTriggerGlobalUpdateEnabled(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) -{ - nsIDOMInstallTriggerGlobal *nativeThis = getTriggerNative(cx, obj); - if (!nativeThis) - return JS_FALSE; - - *rval = JSVAL_FALSE; - - nsIScriptGlobalObject *globalObject = nsnull; - nsIScriptContext *scriptContext = GetScriptContextFromJSContext(cx); - if (scriptContext) - globalObject = scriptContext->GetGlobalObject(); - - bool nativeRet = false; - if (globalObject) - nativeThis->UpdateEnabled(globalObject, XPI_GLOBAL, &nativeRet); - - *rval = BOOLEAN_TO_JSVAL(nativeRet); - return JS_TRUE; -} - - -// -// Native method Install -// -static JSBool -InstallTriggerGlobalInstall(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) -{ - nsIDOMInstallTriggerGlobal *nativeThis = getTriggerNative(cx, obj); - if (!nativeThis) - return JS_FALSE; - - *rval = JSVAL_FALSE; - - // make sure XPInstall is enabled, return false if not - nsIScriptGlobalObject *globalObject = nsnull; - nsIScriptContext *scriptContext = GetScriptContextFromJSContext(cx); - if (scriptContext) - globalObject = scriptContext->GetGlobalObject(); - - if (!globalObject) - return JS_TRUE; - - nsCOMPtr<nsIScriptSecurityManager> secman(do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID)); - if (!secman) - { - JS_ReportError(cx, "Could not the script security manager service."); - return JS_FALSE; - } - // get the principal. if it doesn't exist, die. - nsCOMPtr<nsIPrincipal> principal; - nsresult rv = secman->GetSubjectPrincipal(getter_AddRefs(principal)); - - if (NS_FAILED(rv) || !principal) - { - JS_ReportError(cx, "Could not get the Subject Principal during InstallTrigger.Install()"); - return JS_FALSE; - } - - // get window.location to construct relative URLs - nsCOMPtr<nsIURI> baseURL; - JSObject* global = JS_GetGlobalObject(cx); - if (global) - { - jsval v; - if (JS_GetProperty(cx,global,"location",&v)) - { - nsAutoString location; - ConvertJSValToStr( location, cx, v ); - NS_NewURI(getter_AddRefs(baseURL), location); - } - } - - bool abortLoad = false; - - // parse associative array of installs - if ( argc >= 1 && JSVAL_IS_OBJECT(argv[0]) && JSVAL_TO_OBJECT(argv[0]) ) - { - nsXPITriggerInfo *trigger = new nsXPITriggerInfo(); - if (!trigger) - return JS_FALSE; - - trigger->SetPrincipal(principal); - - JSIdArray *ida = JS_Enumerate( cx, JSVAL_TO_OBJECT(argv[0]) ); - if ( ida ) - { - jsval v; - const PRUnichar *name, *URL; - const PRUnichar *iconURL = nsnull; - - for (int i = 0; i < ida->length && !abortLoad; i++ ) - { - JS_IdToValue( cx, ida->vector[i], &v ); - JSString * str = JS_ValueToString( cx, v ); - if (!str) - { - abortLoad = true; - break; - } - - name = reinterpret_cast<const PRUnichar*>(JS_GetStringChars( str )); - - URL = iconURL = nsnull; - JSAutoByteString hash; - JS_GetUCProperty( cx, JSVAL_TO_OBJECT(argv[0]), reinterpret_cast<const jschar*>(name), nsCRT::strlen(name), &v ); - if ( JSVAL_IS_OBJECT(v) && JSVAL_TO_OBJECT(v) ) - { - jsval v2; - if (JS_GetProperty( cx, JSVAL_TO_OBJECT(v), "URL", &v2 ) && !JSVAL_IS_VOID(v2)) { - JSString *str = JS_ValueToString(cx, v2); - if (!str) { - abortLoad = true; - break; - } - URL = reinterpret_cast<const PRUnichar*>(JS_GetStringChars(str)); - } - - if (JS_GetProperty( cx, JSVAL_TO_OBJECT(v), "IconURL", &v2 ) && !JSVAL_IS_VOID(v2)) { - JSString *str = JS_ValueToString(cx, v2); - if (!str) { - abortLoad = true; - break; - } - iconURL = reinterpret_cast<const PRUnichar*>(JS_GetStringChars(str)); - } - - if (JS_GetProperty( cx, JSVAL_TO_OBJECT(v), "Hash", &v2) && !JSVAL_IS_VOID(v2)) { - JSString *str = JS_ValueToString(cx, v2); - if (!str || !hash.encode(cx, str)) { - abortLoad = true; - break; - } - } - } - else - { - JSString *str = JS_ValueToString(cx, v); - if (!str) { - abortLoad = true; - break; - } - URL = reinterpret_cast<const PRUnichar*>(JS_GetStringChars(str)); - } - - if ( URL ) - { - // Get relative URL to load - nsAutoString xpiURL(URL); - if (baseURL) - { - nsCAutoString resolvedURL; - baseURL->Resolve(NS_ConvertUTF16toUTF8(xpiURL), resolvedURL); - xpiURL = NS_ConvertUTF8toUTF16(resolvedURL); - } - - nsAutoString icon(iconURL); - if (iconURL && baseURL) - { - nsCAutoString resolvedIcon; - baseURL->Resolve(NS_ConvertUTF16toUTF8(icon), resolvedIcon); - icon = NS_ConvertUTF8toUTF16(resolvedIcon); - } - - // Make sure we're allowed to load this URL and the icon URL - rv = InstallTriggerCheckLoadURIFromScript(cx, xpiURL); - if (NS_FAILED(rv)) - abortLoad = true; - - if (!abortLoad && iconURL) - { - rv = InstallTriggerCheckLoadURIFromScript(cx, icon); - if (NS_FAILED(rv)) - abortLoad = true; - } - - if (!abortLoad) - { - // Add the install item to the trigger collection - nsXPITriggerItem *item = - new nsXPITriggerItem( name, xpiURL.get(), icon.get(), hash ); - if ( item ) - { - trigger->Add( item ); - } - else - abortLoad = true; - } - } - else - abortLoad = true; - } - JS_DestroyIdArray( cx, ida ); - } - - - // pass on only if good stuff found - if (!abortLoad && trigger->Size() > 0) - { - nsCOMPtr<nsIURI> checkuri; - rv = nativeThis->GetOriginatingURI(globalObject, - getter_AddRefs(checkuri)); - if (NS_SUCCEEDED(rv)) - { - nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(globalObject); - nsCOMPtr<nsIXPIInstallInfo> installInfo = - new nsXPIInstallInfo(win, checkuri, trigger, 0); - if (installInfo) - { - // installInfo now owns triggers - bool enabled = false; - nativeThis->UpdateEnabled(checkuri, XPI_WHITELIST, &enabled); - if (!enabled) - { - nsCOMPtr<nsIObserverService> os = - mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(installInfo, - "xpinstall-install-blocked", - nsnull); - } - else - { - // save callback function if any (ignore bad args for now) - if ( argc >= 2 && JS_TypeOfValue(cx,argv[1]) == JSTYPE_FUNCTION ) - { - trigger->SaveCallback( cx, argv[1] ); - } - - bool result; - nativeThis->StartInstall(installInfo, &result); - *rval = BOOLEAN_TO_JSVAL(result); - } - return JS_TRUE; - } - } - } - // didn't pass it on so we must delete trigger - delete trigger; - } - - JS_ReportError(cx, "Incorrect arguments to InstallTrigger.Install()"); - return JS_FALSE; -} - - -// -// Native method InstallChrome -// -static JSBool -InstallTriggerGlobalInstallChrome(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) -{ - nsIDOMInstallTriggerGlobal *nativeThis = getTriggerNative(cx, obj); - if (!nativeThis) - return JS_FALSE; - - uint32 chromeType = NOT_CHROME; - nsAutoString sourceURL; - nsAutoString name; - - *rval = JSVAL_FALSE; - - // get chromeType first, the update enabled check for skins skips whitelisting - if (argc >=1) - { - if (!JS_ValueToECMAUint32(cx, argv[0], &chromeType)) - return JS_FALSE; - } - - // make sure XPInstall is enabled, return if not - nsIScriptGlobalObject *globalObject = nsnull; - nsIScriptContext *scriptContext = GetScriptContextFromJSContext(cx); - if (scriptContext) - globalObject = scriptContext->GetGlobalObject(); - - if (!globalObject) - return JS_TRUE; - - // get window.location to construct relative URLs - nsCOMPtr<nsIURI> baseURL; - JSObject* global = JS_GetGlobalObject(cx); - if (global) - { - jsval v; - if (JS_GetProperty(cx,global,"location",&v)) - { - nsAutoString location; - ConvertJSValToStr( location, cx, v ); - NS_NewURI(getter_AddRefs(baseURL), location); - } - } - - - if ( argc >= 3 ) - { - ConvertJSValToStr(sourceURL, cx, argv[1]); - ConvertJSValToStr(name, cx, argv[2]); - - if (baseURL) - { - nsCAutoString resolvedURL; - baseURL->Resolve(NS_ConvertUTF16toUTF8(sourceURL), resolvedURL); - sourceURL = NS_ConvertUTF8toUTF16(resolvedURL); - } - - // Make sure caller is allowed to load this url. - nsresult rv = InstallTriggerCheckLoadURIFromScript(cx, sourceURL); - if (NS_FAILED(rv)) - return JS_FALSE; - - if ( chromeType & CHROME_ALL ) - { - // there's at least one known chrome type - nsCOMPtr<nsIURI> checkuri; - nsresult rv = nativeThis->GetOriginatingURI(globalObject, - getter_AddRefs(checkuri)); - if (NS_SUCCEEDED(rv)) - { - nsAutoPtr<nsXPITriggerInfo> trigger(new nsXPITriggerInfo()); - nsAutoPtr<nsXPITriggerItem> item(new nsXPITriggerItem(name.get(), - sourceURL.get(), - nsnull)); - if (trigger && item) - { - // trigger will free item when complete - trigger->Add(item.forget()); - nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(globalObject); - nsCOMPtr<nsIXPIInstallInfo> installInfo = - new nsXPIInstallInfo(win, checkuri, trigger, chromeType); - if (installInfo) - { - // installInfo owns trigger now - trigger.forget(); - bool enabled = false; - nativeThis->UpdateEnabled(checkuri, XPI_WHITELIST, - &enabled); - if (!enabled) - { - nsCOMPtr<nsIObserverService> os = - mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(installInfo, - "xpinstall-install-blocked", - nsnull); - } - else - { - bool nativeRet = false; - nativeThis->StartInstall(installInfo, &nativeRet); - *rval = BOOLEAN_TO_JSVAL(nativeRet); - } - } - } - } - } - } - return JS_TRUE; -} - - -// -// Native method StartSoftwareUpdate -// -static JSBool -InstallTriggerGlobalStartSoftwareUpdate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) -{ - nsIDOMInstallTriggerGlobal *nativeThis = getTriggerNative(cx, obj); - if (!nativeThis) - return JS_FALSE; - - bool nativeRet; - PRInt32 flags = 0; - - *rval = JSVAL_FALSE; - - nsIScriptGlobalObject *globalObject = nsnull; - nsIScriptContext *scriptContext = GetScriptContextFromJSContext(cx); - if (scriptContext) - globalObject = scriptContext->GetGlobalObject(); - - if (!globalObject) - return JS_TRUE; - - // get window.location to construct relative URLs - nsCOMPtr<nsIURI> baseURL; - JSObject* global = JS_GetGlobalObject(cx); - if (global) - { - jsval v; - if (JS_GetProperty(cx,global,"location",&v)) - { - nsAutoString location; - ConvertJSValToStr( location, cx, v ); - NS_NewURI(getter_AddRefs(baseURL), location); - } - } - - - if ( argc >= 1 ) - { - nsAutoString xpiURL; - ConvertJSValToStr(xpiURL, cx, argv[0]); - if (baseURL) - { - nsCAutoString resolvedURL; - baseURL->Resolve(NS_ConvertUTF16toUTF8(xpiURL), resolvedURL); - xpiURL = NS_ConvertUTF8toUTF16(resolvedURL); - } - - // Make sure caller is allowed to load this url. - nsresult rv = InstallTriggerCheckLoadURIFromScript(cx, xpiURL); - if (NS_FAILED(rv)) - return JS_FALSE; - - if (argc >= 2 && !JS_ValueToInt32(cx, argv[1], (int32 *)&flags)) - { - JS_ReportError(cx, "StartSoftwareUpdate() 2nd parameter must be a number"); - return JS_FALSE; - } - - nsCOMPtr<nsIURI> checkuri; - rv = nativeThis->GetOriginatingURI(globalObject, getter_AddRefs(checkuri)); - if (NS_SUCCEEDED(rv)) - { - nsAutoPtr<nsXPITriggerInfo> trigger(new nsXPITriggerInfo()); - nsAutoPtr<nsXPITriggerItem> item(new nsXPITriggerItem(0, - xpiURL.get(), - nsnull)); - if (trigger && item) - { - // trigger will free item when complete - trigger->Add(item.forget()); - nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(globalObject); - nsCOMPtr<nsIXPIInstallInfo> installInfo = - new nsXPIInstallInfo(win, checkuri, trigger, 0); - if (installInfo) - { - // From here trigger is owned by installInfo until passed on to nsXPInstallManager - trigger.forget(); - bool enabled = false; - nativeThis->UpdateEnabled(checkuri, XPI_WHITELIST, &enabled); - if (!enabled) - { - nsCOMPtr<nsIObserverService> os = - mozilla::services::GetObserverService(); - if (os) - os->NotifyObservers(installInfo, - "xpinstall-install-blocked", - nsnull); - } - else - { - nativeThis->StartInstall(installInfo, &nativeRet); - *rval = BOOLEAN_TO_JSVAL(nativeRet); - } - } - } - } - } - else - { - JS_ReportError(cx, "Function StartSoftwareUpdate requires 1 parameters"); - return JS_FALSE; - } - - return JS_TRUE; -} - - -// -// InstallTriggerGlobal class methods -// -static JSFunctionSpec InstallTriggerGlobalMethods[] = -{ - {"UpdateEnabled", InstallTriggerGlobalUpdateEnabled, 0,0,0}, - {"StartSoftwareUpdate", InstallTriggerGlobalStartSoftwareUpdate, 2,0,0}, - {"updateEnabled", InstallTriggerGlobalUpdateEnabled, 0,0,0}, - {"enabled", InstallTriggerGlobalUpdateEnabled, 0,0,0}, - {"install", InstallTriggerGlobalInstall, 2,0,0}, - {"installChrome", InstallTriggerGlobalInstallChrome, 2,0,0}, - {"startSoftwareUpdate", InstallTriggerGlobalStartSoftwareUpdate, 2,0,0}, - {nsnull,nsnull,0,0,0} -}; - - -static JSConstDoubleSpec diff_constants[] = -{ - { nsIDOMInstallTriggerGlobal::MAJOR_DIFF, "MAJOR_DIFF" }, - { nsIDOMInstallTriggerGlobal::MINOR_DIFF, "MINOR_DIFF" }, - { nsIDOMInstallTriggerGlobal::REL_DIFF, "REL_DIFF" }, - { nsIDOMInstallTriggerGlobal::BLD_DIFF, "BLD_DIFF" }, - { nsIDOMInstallTriggerGlobal::EQUAL, "EQUAL" }, - { nsIDOMInstallTriggerGlobal::NOT_FOUND, "NOT_FOUND" }, - { CHROME_SKIN, "SKIN" }, - { CHROME_LOCALE, "LOCALE" }, - { CHROME_CONTENT, "CONTENT" }, - { CHROME_ALL, "PACKAGE" }, - {0,nsnull} -}; - - - -nsresult InitInstallTriggerGlobalClass(JSContext *jscontext, JSObject *global, void** prototype) -{ - JSObject *proto = nsnull; - - if (prototype != nsnull) - *prototype = nsnull; - - proto = JS_InitClass(jscontext, // context - global, // global object - nsnull, // parent proto - &InstallTriggerGlobalClass, // JSClass - nsnull, // JSNative ctor - nsnull, // ctor args - nsnull, // proto props - nsnull, // proto funcs - nsnull, // ctor props (static) - InstallTriggerGlobalMethods); // ctor funcs (static) - - - if (nsnull == proto) return NS_ERROR_FAILURE; - - if ( false == JS_DefineConstDoubles(jscontext, proto, diff_constants) ) - return NS_ERROR_FAILURE; - - if (prototype != nsnull) - *prototype = proto; - - return NS_OK; -} - - - -// -// InstallTriggerGlobal class initialization -// -nsresult NS_InitInstallTriggerGlobalClass(nsIScriptContext *aContext, void **aPrototype) -{ - JSContext *jscontext = aContext->GetNativeContext(); - JSObject *proto = nsnull; - JSObject *constructor = nsnull; - JSObject *global = JS_GetGlobalObject(jscontext); - jsval vp; - - if ((true != JS_LookupProperty(jscontext, global, "InstallTriggerGlobal", &vp)) || - !JSVAL_IS_OBJECT(vp) || - ((constructor = JSVAL_TO_OBJECT(vp)) == nsnull) || - (true != JS_LookupProperty(jscontext, JSVAL_TO_OBJECT(vp), "prototype", &vp)) || - !JSVAL_IS_OBJECT(vp)) - { - nsresult rv = InitInstallTriggerGlobalClass(jscontext, global, (void**)&proto); - if (NS_FAILED(rv)) return rv; - } - else if ((nsnull != constructor) && JSVAL_IS_OBJECT(vp)) - { - proto = JSVAL_TO_OBJECT(vp); - } - else - { - return NS_ERROR_FAILURE; - } - - if (aPrototype) - *aPrototype = proto; - - return NS_OK; -} - - -// -// Method for creating a new InstallTriggerGlobal JavaScript object -// -nsresult -NS_NewScriptInstallTriggerGlobal(nsIScriptContext *aContext, - nsISupports *aSupports, nsISupports *aParent, - void **aReturn) -{ - NS_PRECONDITION(nsnull != aContext && nsnull != aSupports && - nsnull != aReturn, - "null argument to NS_NewScriptInstallTriggerGlobal"); - - JSObject *proto; - JSObject *parent = nsnull; - JSContext *jscontext = aContext->GetNativeContext(); - nsresult result = NS_OK; - nsIDOMInstallTriggerGlobal *installTriggerGlobal; - - nsCOMPtr<nsIScriptObjectOwner> owner(do_QueryInterface(aParent)); - - if (owner) { - if (NS_OK != owner->GetScriptObject(aContext, (void **)&parent)) { - return NS_ERROR_FAILURE; - } - } else { - nsCOMPtr<nsIScriptGlobalObject> sgo(do_QueryInterface(aParent)); - - if (sgo) { - parent = sgo->GetGlobalJSObject(); - } else { - return NS_ERROR_FAILURE; - } - } - - if (NS_OK != NS_InitInstallTriggerGlobalClass(aContext, (void **)&proto)) { - return NS_ERROR_FAILURE; - } - - result = CallQueryInterface(aSupports, &installTriggerGlobal); - if (NS_OK != result) { - return result; - } - - // create a js object for this class - *aReturn = JS_NewObject(jscontext, &InstallTriggerGlobalClass, proto, parent); - if (nsnull != *aReturn) { - // connect the native object to the js object - JS_SetPrivate(jscontext, (JSObject *)*aReturn, installTriggerGlobal); - } - else { - NS_RELEASE(installTriggerGlobal); - return NS_ERROR_FAILURE; - } - - return NS_OK; -} -
deleted file mode 100644 --- a/xpinstall/src/nsSoftwareUpdate.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nscore.h" -#include "nsIGenericFactory.h" -#include "nsIFactory.h" -#include "nsISupports.h" -#include "nsIComponentManager.h" -#include "nsIComponentRegistrar.h" -#include "nsIServiceManager.h" -#include "nsICategoryManager.h" -#include "nsIScriptNameSpaceManager.h" -#include "nsCURILoader.h" - -#include "nsSoftwareUpdateIIDs.h" - -#include "nsInstallTrigger.h" -#include "nsXPInstallManager.h" - -//---------------------------------------------------------------------- - -// Functions used to create new instances of a given object by the -// generic factory. - -NS_GENERIC_FACTORY_CONSTRUCTOR(nsInstallTrigger) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsXPInstallManager) - -//---------------------------------------------------------------------- - -static NS_METHOD -RegisterInstallTrigger( nsIComponentManager *aCompMgr, - nsIFile *aPath, - const char *registryLocation, - const char *componentType, - const nsModuleComponentInfo *info) -{ - nsresult rv = NS_OK; - - nsCOMPtr<nsICategoryManager> catman = - do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsXPIDLCString previous; - rv = catman->AddCategoryEntry(JAVASCRIPT_GLOBAL_PROPERTY_CATEGORY, - "InstallTrigger", - NS_INSTALLTRIGGERCOMPONENT_CONTRACTID, - true, true, getter_Copies(previous)); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - - -// The list of components we register -static const nsModuleComponentInfo components[] = -{ - { "InstallTrigger Component", - NS_SoftwareUpdateInstallTrigger_CID, - NS_INSTALLTRIGGERCOMPONENT_CONTRACTID, - nsInstallTriggerConstructor, - RegisterInstallTrigger - }, - - { "XPInstall Content Handler", - NS_SoftwareUpdateInstallTrigger_CID, - NS_CONTENT_HANDLER_CONTRACTID_PREFIX"application/x-xpinstall", - nsInstallTriggerConstructor - }, - - { "XPInstallManager Component", - NS_XPInstallManager_CID, - NS_XPINSTALLMANAGERCOMPONENT_CONTRACTID, - nsXPInstallManagerConstructor - } -}; - -NS_IMPL_NSGETMODULE(nsSoftwareUpdate, components)
deleted file mode 100644 --- a/xpinstall/src/nsXPIInstallInfo.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla XPInstall. - * - * The Initial Developer of the Original Code is - * Dave Townsend <dtownsend@oxymoronical.com>. - * - * Portions created by the Initial Developer are Copyright (C) 2007 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** - */ - -#include "nsXPIInstallInfo.h" - -NS_IMPL_ISUPPORTS1(nsXPIInstallInfo, nsIXPIInstallInfo) - -nsXPIInstallInfo::nsXPIInstallInfo(nsIDOMWindow *aOriginatingWindow, - nsIURI *aOriginatingURI, - nsXPITriggerInfo *aTriggerInfo, - PRUint32 aChromeType) - : mOriginatingWindow(aOriginatingWindow), mOriginatingURI(aOriginatingURI), - mTriggerInfo(aTriggerInfo), mChromeType(aChromeType) -{ -} - -nsXPIInstallInfo::~nsXPIInstallInfo() -{ - delete mTriggerInfo; -} - -/* [noscript, notxpcom] attribute triggerInfoPtr triggerInfo; */ -NS_IMETHODIMP -nsXPIInstallInfo::GetTriggerInfo(nsXPITriggerInfo * *aTriggerInfo) -{ - *aTriggerInfo = mTriggerInfo; - return NS_OK; -} - -NS_IMETHODIMP -nsXPIInstallInfo::SetTriggerInfo(nsXPITriggerInfo * aTriggerInfo) -{ - mTriggerInfo = aTriggerInfo; - return NS_OK; -} - -/* readonly attribute nsIDOMWindow originatingWindow; */ -NS_IMETHODIMP -nsXPIInstallInfo::GetOriginatingWindow(nsIDOMWindow * *aOriginatingWindow) -{ - NS_IF_ADDREF(*aOriginatingWindow = mOriginatingWindow); - return NS_OK; -} - -/* readonly attribute nsIURI uri; */ -NS_IMETHODIMP -nsXPIInstallInfo::GetOriginatingURI(nsIURI * *aOriginatingURI) -{ - NS_IF_ADDREF(*aOriginatingURI = mOriginatingURI); - return NS_OK; -} - -/* readonly attribute PRUint32 type; */ -NS_IMETHODIMP -nsXPIInstallInfo::GetChromeType(PRUint32 *aChromeType) -{ - *aChromeType = mChromeType; - return NS_OK; -}
deleted file mode 100644 --- a/xpinstall/src/nsXPIInstallInfo.h +++ /dev/null @@ -1,62 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla XPInstall. - * - * The Initial Developer of the Original Code is - * Dave Townsend <dtownsend@oxymoronical.com>. - * - * Portions created by the Initial Developer are Copyright (C) 2007 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** - */ - -#include "nsXPITriggerInfo.h" -#include "nsIXPIInstallInfo.h" -#include "nsIDOMWindow.h" -#include "nsIDocShell.h" -#include "nsIURI.h" - -class nsXPIInstallInfo : public nsIXPIInstallInfo -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIXPIINSTALLINFO - - nsXPIInstallInfo(nsIDOMWindow *aOriginatingWindow, - nsIURI *aOriginatingURI, nsXPITriggerInfo *aTriggerInfo, - PRUint32 aChromeType); - -private: - ~nsXPIInstallInfo(); - - nsCOMPtr<nsIDOMWindow> mOriginatingWindow; - nsCOMPtr<nsIURI> mOriginatingURI; - nsXPITriggerInfo* mTriggerInfo; - PRUint32 mChromeType; -};
deleted file mode 100644 --- a/xpinstall/src/nsXPITriggerInfo.cpp +++ /dev/null @@ -1,355 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998-1999 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "mozilla/Util.h" - -#include "jscntxt.h" -#include "nscore.h" -#include "plstr.h" -#include "nsXPITriggerInfo.h" -#include "nsNetUtil.h" -#include "nsDebug.h" -#include "nsAutoPtr.h" -#include "nsThreadUtils.h" -#include "nsIServiceManager.h" -#include "nsIJSContextStack.h" -#include "nsIScriptSecurityManager.h" -#include "nsICryptoHash.h" -#include "nsIX509Cert.h" - -using namespace mozilla; - -// -// nsXPITriggerItem -// - -nsXPITriggerItem::nsXPITriggerItem( const PRUnichar* aName, - const PRUnichar* aURL, - const PRUnichar* aIconURL, - const char* aHash, - PRInt32 aFlags) - : mName(aName), mURL(aURL), mIconURL(aIconURL), mHashFound(false), mFlags(aFlags) -{ - MOZ_COUNT_CTOR(nsXPITriggerItem); - - // check for arguments - PRInt32 qmark = mURL.FindChar('?'); - if ( qmark != kNotFound ) - { - mArguments = Substring( mURL, qmark+1, mURL.Length() ); - } - - // construct name if not passed in - if ( mName.IsEmpty() ) - { - // Use the filename as the display name by starting after the last - // slash in the URL, looking backwards from the arguments delimiter if - // we found one. By good fortune using kNotFound as the offset for - // RFindChar() starts at the end, so we can use qmark in all cases. - - PRInt32 namestart = mURL.RFindChar( '/', qmark ); - - // the real start is after the slash (or 0 if not found) - namestart = ( namestart==kNotFound ) ? 0 : namestart + 1; - - PRInt32 length; - if (qmark == kNotFound) - length = mURL.Length(); // no '?', slurp up rest of URL - else - length = (qmark - namestart); // filename stops at the '?' - - mName = Substring( mURL, namestart, length ); - } - - // parse optional hash into its parts - if (aHash) - { - mHashFound = true; - - char * colon = PL_strchr(aHash, ':'); - if (colon) - { - mHasher = do_CreateInstance("@mozilla.org/security/hash;1"); - if (!mHasher) return; - - *colon = '\0'; // null the colon so that aHash is just the type. - nsresult rv = mHasher->InitWithString(nsDependentCString(aHash)); - *colon = ':'; // restore the colon - - if (NS_SUCCEEDED(rv)) - mHash = colon+1; - } - } -} - -nsXPITriggerItem::~nsXPITriggerItem() -{ - MOZ_COUNT_DTOR(nsXPITriggerItem); -} - -const PRUnichar* -nsXPITriggerItem::GetSafeURLString() -{ - // create the safe url string the first time - if (mSafeURL.IsEmpty() && !mURL.IsEmpty()) - { - nsCOMPtr<nsIURI> uri; - NS_NewURI(getter_AddRefs(uri), mURL); - if (uri) - { - nsCAutoString spec; - uri->SetUserPass(EmptyCString()); - uri->GetSpec(spec); - mSafeURL = NS_ConvertUTF8toUTF16(spec); - } - } - - return mSafeURL.get(); -} - -void -nsXPITriggerItem::SetPrincipal(nsIPrincipal* aPrincipal) -{ - mPrincipal = aPrincipal; - - // aPrincipal can be null for various failure cases. - // see bug 213894 for an example. - // nsXPInstallManager::OnCertAvailable can be called with a null principal - // and it can also force a null principal. - if (!aPrincipal) - return; - - bool hasCert; - aPrincipal->GetHasCertificate(&hasCert); - if (hasCert) { - nsCOMPtr<nsISupports> certificate; - aPrincipal->GetCertificate(getter_AddRefs(certificate)); - - nsCOMPtr<nsIX509Cert> x509 = do_QueryInterface(certificate); - if (x509) { - x509->GetCommonName(mCertName); - if (mCertName.Length() > 0) - return; - } - - nsCAutoString prettyName; - aPrincipal->GetPrettyName(prettyName); - CopyUTF8toUTF16(prettyName, mCertName); - } -} - -// -// nsXPITriggerInfo -// - -nsXPITriggerInfo::nsXPITriggerInfo() - : mCx(0), mCbval(JSVAL_NULL) -{ - MOZ_COUNT_CTOR(nsXPITriggerInfo); -} - -nsXPITriggerInfo::~nsXPITriggerInfo() -{ - nsXPITriggerItem* item; - - for(PRUint32 i=0; i < Size(); i++) - { - item = Get(i); - delete item; - } - mItems.Clear(); - - if ( mCx && !JSVAL_IS_NULL(mCbval) ) { - JS_BeginRequest(mCx); - JS_RemoveValueRoot(mCx, &mCbval ); - JS_EndRequest(mCx); - } - - MOZ_COUNT_DTOR(nsXPITriggerInfo); -} - -void nsXPITriggerInfo::SaveCallback( JSContext *aCx, jsval aVal ) -{ - NS_ASSERTION( mCx == 0, "callback set twice, memory leak" ); - // We'll only retain the callback if we can get a strong reference to the - // context. - if (!(JS_GetOptions(aCx) & JSOPTION_PRIVATE_IS_NSISUPPORTS)) - return; - mContextWrapper = static_cast<nsISupports *>(JS_GetContextPrivate(aCx)); - if (!mContextWrapper) - return; - - mCx = aCx; - mCbval = aVal; - mThread = do_GetCurrentThread(); - - if ( !JSVAL_IS_NULL(mCbval) ) { - JS_BeginRequest(mCx); - JS_AddValueRoot(mCx, &mCbval ); - JS_EndRequest(mCx); - } -} - -XPITriggerEvent::~XPITriggerEvent() -{ - JS_BeginRequest(cx); - JS_RemoveValueRoot(cx, &cbval); - JS_EndRequest(cx); -} - -NS_IMETHODIMP -XPITriggerEvent::Run() -{ - JSAutoRequest ar(cx); - - // If Components doesn't exist in the global object then XPConnect has - // been torn down, probably because the page was closed. Bail out if that - // is the case. - JSObject* innerGlobal = JS_GetGlobalForObject(cx, JSVAL_TO_OBJECT(cbval)); - jsval components; - if (!JS_LookupProperty(cx, innerGlobal, "Components", &components) || - !JSVAL_IS_OBJECT(components)) - return 0; - - // Build arguments into rooted jsval array - jsval args[2] = { JSVAL_NULL, JSVAL_NULL }; - js::AutoArrayRooter tvr(cx, ArrayLength(args), args); - - // args[0] is the URL - JSString *str = JS_NewUCStringCopyZ(cx, reinterpret_cast<const jschar*>(URL.get())); - if (!str) - return 0; - args[0] = STRING_TO_JSVAL(str); - - // args[1] is the status - if (!JS_NewNumberValue(cx, status, &args[1])) - return 0; - - class StackPushGuard { - nsCOMPtr<nsIJSContextStack> mStack; - public: - StackPushGuard(JSContext *cx) - : mStack(do_GetService("@mozilla.org/js/xpc/ContextStack;1")) - { - if (mStack) - mStack->Push(cx); - } - - ~StackPushGuard() - { - if (mStack) - mStack->Pop(nsnull); - } - } stackPushGuard(cx); - - nsCOMPtr<nsIScriptSecurityManager> secman = - do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); - if (!secman) - { - JS_ReportError(cx, "Could not get script security manager service"); - return 0; - } - - nsCOMPtr<nsIPrincipal> principal; - nsresult rv = secman->GetSubjectPrincipal(getter_AddRefs(principal)); - - if (NS_FAILED(rv) || !principal) - { - JS_ReportError(cx, "Could not get principal from script security manager"); - return 0; - } - - bool equals = false; - principal->Equals(princ, &equals); - if (!equals) - { - JS_ReportError(cx, "Principal of callback context is different than InstallTriggers"); - return 0; - } - - jsval ret; - JS_CallFunctionValue(cx, - JS_GetGlobalObject(cx), - cbval, - 2, - args, - &ret); - return 0; -} - - -void nsXPITriggerInfo::SendStatus(const PRUnichar* URL, PRInt32 status) -{ - nsresult rv; - - if ( mCx && mContextWrapper && !JSVAL_IS_NULL(mCbval) ) - { - // create event and post it - nsRefPtr<XPITriggerEvent> event = new XPITriggerEvent(); - if (event) - { - event->URL = URL; - event->status = status; - event->cx = mCx; - event->princ = mPrincipal; - - event->cbval = mCbval; - JS_BeginRequest(event->cx); - JS_AddNamedValueRoot(event->cx, &event->cbval, - "XPITriggerEvent::cbval" ); - JS_EndRequest(event->cx); - - // Hold a strong reference to keep the underlying - // JSContext from dying before we handle this event. - event->ref = mContextWrapper; - - rv = mThread->Dispatch(event, NS_DISPATCH_NORMAL); - } - else - rv = NS_ERROR_OUT_OF_MEMORY; - - if ( NS_FAILED( rv ) ) - { - // couldn't get event queue -- maybe window is gone or - // some similarly catastrophic occurrence - NS_WARNING("failed to dispatch XPITriggerEvent"); - } - } -}
deleted file mode 100644 --- a/xpinstall/src/nsXPITriggerInfo.h +++ /dev/null @@ -1,144 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998-1999 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef nsXPITriggerInfo_h -#define nsXPITriggerInfo_h - -#include "nsString.h" -#include "nsVoidArray.h" -#include "nsCOMPtr.h" -#include "nsISupportsUtils.h" -#include "nsILocalFile.h" -#include "nsIOutputStream.h" -#include "jsapi.h" -#include "prthread.h" -#include "nsIXPConnect.h" -#include "nsICryptoHash.h" -#include "nsIPrincipal.h" -#include "nsThreadUtils.h" - -struct XPITriggerEvent : public nsRunnable { - nsString URL; - PRInt32 status; - JSContext* cx; - jsval cbval; - nsCOMPtr<nsISupports> ref; - nsCOMPtr<nsIPrincipal> princ; - - virtual ~XPITriggerEvent(); - NS_IMETHOD Run(); -}; - -class nsXPITriggerItem -{ - public: - nsXPITriggerItem( const PRUnichar* name, - const PRUnichar* URL, - const PRUnichar* iconURL, - const char* hash = nsnull, - PRInt32 flags = 0); - ~nsXPITriggerItem(); - - nsString mName; - nsString mURL; - nsString mIconURL; - nsString mArguments; - nsString mCertName; - - bool mHashFound; // this flag indicates that we found _some_ hash info in the trigger - nsCString mHash; - nsCOMPtr<nsICryptoHash> mHasher; - PRInt32 mFlags; - - nsCOMPtr<nsILocalFile> mFile; - nsCOMPtr<nsIOutputStream> mOutStream; - nsCOMPtr<nsIPrincipal> mPrincipal; - - void SetPrincipal(nsIPrincipal* aPrincipal); - - bool IsFileURL() { return StringBeginsWith(mURL, NS_LITERAL_STRING("file:/")); } - - const PRUnichar* GetSafeURLString(); - - private: - //-- prevent inadvertent copies and assignments - nsXPITriggerItem& operator=(const nsXPITriggerItem& rhs); - nsXPITriggerItem(const nsXPITriggerItem& rhs); - - nsString mSafeURL; -}; - - - -class nsXPITriggerInfo -{ - public: - nsXPITriggerInfo(); - ~nsXPITriggerInfo(); - - void Add( nsXPITriggerItem *aItem ) - { if ( aItem ) mItems.AppendElement( (void*)aItem ); } - - nsXPITriggerItem* Get( PRUint32 aIndex ) - { return (nsXPITriggerItem*)mItems.ElementAt(aIndex);} - - void SaveCallback( JSContext *aCx, jsval aVal ); - - PRUint32 Size() { return mItems.Count(); } - - void SendStatus(const PRUnichar* URL, PRInt32 status); - - void SetPrincipal(nsIPrincipal* aPrinc) { mPrincipal = aPrinc; } - - - private: - nsVoidArray mItems; - JSContext *mCx; - nsCOMPtr<nsISupports> mContextWrapper; - jsval mCbval; - nsCOMPtr<nsIThread> mThread; - - nsCOMPtr<nsIPrincipal> mPrincipal; - - //-- prevent inadvertent copies and assignments - nsXPITriggerInfo& operator=(const nsXPITriggerInfo& rhs); - nsXPITriggerInfo(const nsXPITriggerInfo& rhs); -}; - -#endif /* nsXPITriggerInfo_h */
deleted file mode 100644 --- a/xpinstall/src/nsXPInstallManager.cpp +++ /dev/null @@ -1,1414 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nscore.h" -#include "prprf.h" -#include "plstr.h" - -#include "nsISupports.h" -#include "nsIServiceManager.h" - -#include "nsIURL.h" -#include "nsIFileURL.h" - -#include "nsITransport.h" -#include "nsIOutputStream.h" -#include "nsNetUtil.h" -#include "nsIInputStream.h" -#include "nsIFileStreams.h" -#include "nsIStreamListener.h" -#include "nsICryptoHash.h" - -#include "nsIExtensionManager.h" -#include "nsSoftwareUpdateIIDs.h" - -#include "nsIStringEnumerator.h" -#include "nsXPITriggerInfo.h" -#include "nsXPInstallManager.h" -#include "nsInstallTrigger.h" -#include "nsIWindowWatcher.h" -#include "nsIAuthPrompt.h" -#include "nsIWindowMediator.h" -#include "nsIDocument.h" -#include "nsIDOMDocument.h" -#include "nsIDOMWindow.h" -#include "nsDirectoryService.h" -#include "nsDirectoryServiceDefs.h" -#include "nsAppDirectoryServiceDefs.h" - -#include "nsReadableUtils.h" -#include "nsIPromptService.h" -#include "nsIScriptGlobalObject.h" -#include "nsXPCOM.h" -#include "nsISupportsPrimitives.h" -#include "nsIObserverService.h" - -#include "nsISSLStatusProvider.h" -#include "nsISSLStatus.h" -#include "nsIX509Cert.h" -#include "nsIX509Cert3.h" - -#include "nsIPrefService.h" -#include "nsIPrefBranch.h" - -#include "CertReader.h" - -#include "nsEmbedCID.h" -#include "nsIAsyncVerifyRedirectCallback.h" - -#define PREF_XPINSTALL_ENABLED "xpinstall.enabled" -#define PREF_XPINSTALL_CONFIRM_DLG "xpinstall.dialog.confirm" -#define PREF_XPINSTALL_STATUS_DLG_SKIN "xpinstall.dialog.progress.skin" -#define PREF_XPINSTALL_STATUS_DLG_CHROME "xpinstall.dialog.progress.chrome" -#define PREF_XPINSTALL_STATUS_DLG_TYPE_SKIN "xpinstall.dialog.progress.type.skin" -#define PREF_XPINSTALL_STATUS_DLG_TYPE_CHROME "xpinstall.dialog.progress.type.chrome" - -static NS_DEFINE_IID(kZipReaderCID, NS_ZIPREADER_CID); - - -nsXPInstallManager::nsXPInstallManager() - : mTriggers(0), mItem(0), mNextItem(0), mChromeType(NOT_CHROME), - mContentLength(0), mDialogOpen(false), mCancelled(false), - mNeedsShutdown(false), mFromChrome(false) -{ - // we need to own ourself because we have a longer - // lifetime than the scriptlet that created us. - NS_ADDREF_THIS(); -} - - -nsXPInstallManager::~nsXPInstallManager() -{ - NS_ASSERT_OWNINGTHREAD(nsXPInstallManager); - NS_ASSERTION(!mTriggers, "Shutdown not called, triggers still alive"); -} - - -NS_INTERFACE_MAP_BEGIN(nsXPInstallManager) - NS_INTERFACE_MAP_ENTRY(nsIXPIDialogService) - NS_INTERFACE_MAP_ENTRY(nsIXPInstallManager) - NS_INTERFACE_MAP_ENTRY(nsIObserver) - NS_INTERFACE_MAP_ENTRY(nsIStreamListener) - NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) - NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) - NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) - NS_INTERFACE_MAP_ENTRY(nsPICertNotification) - NS_INTERFACE_MAP_ENTRY(nsIBadCertListener2) - NS_INTERFACE_MAP_ENTRY(nsISSLErrorListener) - NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) - NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISupportsWeakReference) -NS_INTERFACE_MAP_END - -NS_IMPL_THREADSAFE_ADDREF(nsXPInstallManager) -NS_IMPL_THREADSAFE_RELEASE(nsXPInstallManager) - -NS_IMETHODIMP -nsXPInstallManager::InitManagerFromChrome(const PRUnichar **aURLs, - PRUint32 aURLCount, - nsIXPIProgressDialog* aListener) -{ - return InitManagerWithHashes(aURLs, nsnull, aURLCount, aListener); -} - -NS_IMETHODIMP -nsXPInstallManager::InitManagerWithHashes(const PRUnichar **aURLs, - const char **aHashes, - PRUint32 aURLCount, - nsIXPIProgressDialog* aListener) -{ - // If Software Installation is not enabled, we don't want to proceed with - // update. - bool xpinstallEnabled = true; - nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID)); - if (pref) - pref->GetBoolPref(PREF_XPINSTALL_ENABLED, &xpinstallEnabled); - - if (!xpinstallEnabled) - return NS_OK; - - mTriggers = new nsXPITriggerInfo(); - if (!mTriggers) - return NS_ERROR_OUT_OF_MEMORY; - - mNeedsShutdown = true; - - for (PRUint32 i = 0; i < aURLCount; ++i) - { - nsXPITriggerItem* item = new nsXPITriggerItem(0, aURLs[i], nsnull, - aHashes ? aHashes[i] : nsnull); - if (!item) - { - delete mTriggers; // nsXPITriggerInfo frees any alloc'ed nsXPITriggerItems - mTriggers = nsnull; - Shutdown(); - return NS_ERROR_OUT_OF_MEMORY; - } - mTriggers->Add(item); - } - - mFromChrome = true; - - nsresult rv = Observe(aListener, XPI_PROGRESS_TOPIC, NS_LITERAL_STRING("open").get()); - if (NS_FAILED(rv)) - Shutdown(); - return rv; -} - -NS_IMETHODIMP -nsXPInstallManager::InitManagerWithInstallInfo(nsIXPIInstallInfo* aInstallInfo) -{ - nsXPITriggerInfo* triggers; - nsresult rv = aInstallInfo->GetTriggerInfo(&triggers); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsIDOMWindow> win; - rv = aInstallInfo->GetOriginatingWindow(getter_AddRefs(win)); - if (NS_SUCCEEDED(rv)) - { - PRUint32 type; - rv = aInstallInfo->GetChromeType(&type); - if (NS_SUCCEEDED(rv)) - { - // Passing ownership onto InitManager which will free when necessary - aInstallInfo->SetTriggerInfo(nsnull); - return InitManager(win, triggers, type); - } - } - - NS_RELEASE_THIS(); - return rv; -} - -NS_IMETHODIMP -nsXPInstallManager::InitManager(nsIDOMWindow* aParentWindow, nsXPITriggerInfo* aTriggers, PRUint32 aChromeType) -{ - if ( !aTriggers || aTriggers->Size() == 0 ) - { - NS_WARNING("XPInstallManager called with no trigger info!"); - delete aTriggers; - NS_RELEASE_THIS(); - return NS_ERROR_INVALID_POINTER; - } - - nsresult rv = NS_OK; - - mNeedsShutdown = true; - mTriggers = aTriggers; - mChromeType = aChromeType; - - mParentWindow = aParentWindow; - - // Attempt to find a load group, continue if we can't find one though - if (aParentWindow) { - nsCOMPtr<nsIDOMDocument> domdoc; - rv = aParentWindow->GetDocument(getter_AddRefs(domdoc)); - if (NS_SUCCEEDED(rv) && domdoc) { - nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc); - if (doc) - mLoadGroup = doc->GetDocumentLoadGroup(); - } - } - - // Start downloading initial chunks looking for signatures, - mOutstandingCertLoads = mTriggers->Size(); - - nsXPITriggerItem *item = mTriggers->Get(--mOutstandingCertLoads); - - nsCOMPtr<nsIURI> uri; - NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(item->mURL)); - nsCOMPtr<nsIStreamListener> listener = new CertReader(uri, nsnull, this); - if (listener) - rv = NS_OpenURI(listener, nsnull, uri, nsnull, mLoadGroup); - else - rv = NS_ERROR_OUT_OF_MEMORY; - - if (NS_FAILED(rv)) { - Shutdown(); - } - return rv; -} - - -nsresult -nsXPInstallManager::InitManagerInternal() -{ - nsresult rv; - bool OKtoInstall = false; // initialize to secure state - - //----------------------------------------------------- - // *** Do not return early after this point *** - // - // We have to clean up the triggers in case of error - //----------------------------------------------------- - - // --- use embedding dialogs if any registered - nsCOMPtr<nsIXPIDialogService> dlgSvc(do_CreateInstance(NS_XPIDIALOGSERVICE_CONTRACTID)); - if ( !dlgSvc ) - dlgSvc = this; // provide our own dialogs - - // --- prepare dialog params - PRUint32 numTriggers = mTriggers->Size(); - PRUint32 numStrings = 4 * numTriggers; - const PRUnichar** packageList = - (const PRUnichar**)malloc( sizeof(PRUnichar*) * numStrings ); - - if ( packageList ) - { - // populate the list. The list doesn't own the strings - for ( PRUint32 i=0, j=0; i < numTriggers; i++ ) - { - nsXPITriggerItem *item = mTriggers->Get(i); - packageList[j++] = item->mName.get(); - packageList[j++] = item->GetSafeURLString(); - packageList[j++] = item->mIconURL.get(); - packageList[j++] = item->mCertName.get(); - } - - //----------------------------------------------------- - // Get permission to install - //----------------------------------------------------- - -#ifdef ENABLE_SKIN_SIMPLE_INSTALLATION_UI - if ( mChromeType == CHROME_SKIN ) - { - // We may want to enable the simple installation UI once - // bug 343037 is fixed - - // skins get a simpler/friendlier dialog - // XXX currently not embeddable - OKtoInstall = ConfirmChromeInstall( mParentWindow, packageList ); - } - else - { -#endif - rv = dlgSvc->ConfirmInstall( mParentWindow, - packageList, - numStrings, - &OKtoInstall ); - if (NS_FAILED(rv)) - OKtoInstall = false; -#ifdef ENABLE_SKIN_SIMPLE_INSTALLATION_UI - } -#endif - - if (OKtoInstall) - { - //----------------------------------------------------- - // Open the progress dialog - //----------------------------------------------------- - - rv = dlgSvc->OpenProgressDialog( packageList, numStrings, this ); - } - } - else - rv = NS_ERROR_OUT_OF_MEMORY; - - //----------------------------------------------------- - // cleanup and signal callbacks if there were errors - //----------------------------------------------------- - - if (packageList) - free(packageList); - - PRInt32 cbstatus = 0; // callback status - if (NS_FAILED(rv)) - cbstatus = nsInstall::UNEXPECTED_ERROR; - else if (!OKtoInstall) - cbstatus = nsInstall::USER_CANCELLED; - - if ( cbstatus != 0 ) - { - // --- must shutdown if not continuing - Shutdown( cbstatus ); - } - - return rv; -} - - -NS_IMETHODIMP -nsXPInstallManager::ConfirmInstall(nsIDOMWindow *aParent, const PRUnichar **aPackageList, PRUint32 aCount, bool *aRetval) -{ - *aRetval = false; - - nsCOMPtr<nsIDOMWindow> parentWindow = aParent; - nsCOMPtr<nsIDialogParamBlock> params; - nsresult rv = LoadParams( aCount, aPackageList, getter_AddRefs(params) ); - - if ( NS_SUCCEEDED(rv) && parentWindow && params) - { - nsCOMPtr<nsIDOMWindow> newWindow; - - nsCOMPtr<nsISupportsInterfacePointer> ifptr = - do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - ifptr->SetData(params); - ifptr->SetDataIID(&NS_GET_IID(nsIDialogParamBlock)); - - char* confirmDialogURL; - nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); - if (!pref) - return rv; - - rv = pref->GetCharPref(PREF_XPINSTALL_CONFIRM_DLG, &confirmDialogURL); - NS_ASSERTION(NS_SUCCEEDED(rv), "Can't invoke XPInstall FE without a FE URL! Set xpinstall.dialog.confirm"); - if (NS_FAILED(rv)) - return rv; - - rv = parentWindow->OpenDialog(NS_ConvertASCIItoUTF16(confirmDialogURL), - NS_LITERAL_STRING("_blank"), - NS_LITERAL_STRING("chrome,centerscreen,modal,titlebar"), - ifptr, - getter_AddRefs(newWindow)); - - if (NS_SUCCEEDED(rv)) - { - //Now get which button was pressed from the ParamBlock - PRInt32 buttonPressed = 0; - params->GetInt( 0, &buttonPressed ); - *aRetval = buttonPressed ? false : true; - } - } - - return rv; -} - -#ifdef ENABLE_SKIN_SIMPLE_INSTALLATION_UI -bool nsXPInstallManager::ConfirmChromeInstall(nsIDOMWindow* aParentWindow, const PRUnichar **aPackage) -{ - // get the dialog strings - nsXPIDLString applyNowText; - nsXPIDLString confirmText; - nsCOMPtr<nsIStringBundleService> bundleSvc = - do_GetService(NS_STRINGBUNDLE_CONTRACTID); - if (!bundleSvc) - return false; - - nsCOMPtr<nsIStringBundle> xpiBundle; - bundleSvc->CreateBundle( XPINSTALL_BUNDLE_URL, - getter_AddRefs(xpiBundle) ); - if (!xpiBundle) - return false; - - const PRUnichar *formatStrings[2] = { aPackage[0], aPackage[1] }; - if ( mChromeType == CHROME_LOCALE ) - { - xpiBundle->GetStringFromName( - NS_LITERAL_STRING("ApplyNowLocale").get(), - getter_Copies(applyNowText)); - xpiBundle->FormatStringFromName( - NS_LITERAL_STRING("ConfirmLocale").get(), - formatStrings, - 2, - getter_Copies(confirmText)); - } - else - { - xpiBundle->GetStringFromName( - NS_LITERAL_STRING("ApplyNowSkin").get(), - getter_Copies(applyNowText)); - xpiBundle->FormatStringFromName( - NS_LITERAL_STRING("ConfirmSkin").get(), - formatStrings, - 2, - getter_Copies(confirmText)); - } - - if (confirmText.IsEmpty()) - return false; - - // confirmation dialog - bool bInstall = false; - nsCOMPtr<nsIPromptService> dlgService(do_GetService(NS_PROMPTSERVICE_CONTRACTID)); - if (dlgService) - { - dlgService->Confirm( - aParentWindow, - nsnull, - confirmText, - &bInstall ); - } - - return bInstall; -} -#endif - -NS_IMETHODIMP -nsXPInstallManager::OpenProgressDialog(const PRUnichar **aPackageList, PRUint32 aCount, nsIObserver *aObserver) -{ - // --- convert parameters into nsISupportArray members - nsCOMPtr<nsIDialogParamBlock> list; - nsresult rv = LoadParams( aCount, aPackageList, getter_AddRefs(list) ); - if (NS_FAILED(rv)) - return rv; - - nsCOMPtr<nsISupportsInterfacePointer> listwrap(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID)); - if (listwrap) { - listwrap->SetData(list); - listwrap->SetDataIID(&NS_GET_IID(nsIDialogParamBlock)); - } - - nsCOMPtr<nsISupportsInterfacePointer> callbackwrap(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID)); - if (callbackwrap) { - callbackwrap->SetData(aObserver); - callbackwrap->SetDataIID(&NS_GET_IID(nsIObserver)); - } - - nsCOMPtr<nsISupportsArray> params(do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID)); - - if ( !params || !listwrap || !callbackwrap ) - return NS_ERROR_FAILURE; - - params->AppendElement(listwrap); - params->AppendElement(callbackwrap); - - // --- open the window - nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); - if (!wwatch) - return rv; - - char *statusDialogURL, *statusDialogType; - nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); - if (!pref) - return rv; - const char* statusDlg = mChromeType == CHROME_SKIN ? PREF_XPINSTALL_STATUS_DLG_SKIN - : PREF_XPINSTALL_STATUS_DLG_CHROME; - rv = pref->GetCharPref(statusDlg, &statusDialogURL); - NS_ASSERTION(NS_SUCCEEDED(rv), "Can't invoke XPInstall FE without a FE URL! Set xpinstall.dialog.status"); - if (NS_FAILED(rv)) - return rv; - - const char* statusType = mChromeType == CHROME_SKIN ? PREF_XPINSTALL_STATUS_DLG_TYPE_SKIN - : PREF_XPINSTALL_STATUS_DLG_TYPE_CHROME; - rv = pref->GetCharPref(statusType, &statusDialogType); - nsAutoString type; - type.AssignWithConversion(statusDialogType); - if (NS_SUCCEEDED(rv) && !type.IsEmpty()) { - nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); - - nsCOMPtr<nsIDOMWindow> recentWindow; - wm->GetMostRecentWindow(type.get(), getter_AddRefs(recentWindow)); - if (recentWindow) { - nsCOMPtr<nsIObserverService> os = - mozilla::services::GetObserverService(); - os->NotifyObservers(params, "xpinstall-download-started", nsnull); - - recentWindow->Focus(); - return NS_OK; - } - } - - nsCOMPtr<nsIDOMWindow> newWindow; - rv = wwatch->OpenWindow(0, - statusDialogURL, - "_blank", - "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable", - params, - getter_AddRefs(newWindow)); - - return rv; -} - - -NS_IMETHODIMP nsXPInstallManager::Observe( nsISupports *aSubject, - const char *aTopic, - const PRUnichar *aData ) -{ - nsresult rv = NS_ERROR_ILLEGAL_VALUE; - - if ( !aTopic || !aData ) - return rv; - - nsDependentCString topic( aTopic ); - if ( topic.Equals( XPI_PROGRESS_TOPIC ) ) - { - //------------------------------------------------------ - // Communication from the XPInstall Progress Dialog - //------------------------------------------------------ - - nsDependentString data( aData ); - - if ( data.Equals( NS_LITERAL_STRING("open") ) ) - { - // -- The dialog has been opened - if (mDialogOpen) - return NS_OK; // We've already been opened, nothing more to do - - mDialogOpen = true; - rv = NS_OK; - - nsCOMPtr<nsIObserverService> os = - mozilla::services::GetObserverService(); - if (os) - { - os->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, true); - os->AddObserver(this, "quit-application", true); - } - - mDlg = do_QueryInterface(aSubject); - - // -- get the ball rolling - DownloadNext(); - } - - else if ( data.Equals( NS_LITERAL_STRING("cancel") ) ) - { - // -- The dialog/user wants us to cancel the download - mCancelled = true; - if ( !mDialogOpen ) - { - // if we've never been opened then we can shutdown right here, - // otherwise we need to let mCancelled get discovered elsewhere - Shutdown(); - } - rv = NS_OK; - } - } - else if ( topic.Equals( NS_IOSERVICE_GOING_OFFLINE_TOPIC ) || - topic.Equals( "quit-application" ) ) - { - mCancelled = true; - rv = NS_OK; - } - - return rv; -} - - -/////////////////////////////////////////////////////////////////////////////////////////////// -// Function name : VerifySigning -// Description : Verify that the entire zip file is signed by the certificate displayed to -// the user during download -// Return type : PRInt32 -// Argument : nsIZipReader* hZip - the zip reader -// Argument : nsIPrincipal* aPrincipal - a principal, if any, displayed to the user -// during download. Would have been retrieved from the first file in the zip -/////////////////////////////////////////////////////////////////////////////////////////////// - -static nsresult -VerifySigning(nsIZipReader* hZip, nsIPrincipal* aPrincipal) -{ - // If we didn't detect a principal from the zip file during download then - // we didn't suggest it was signed to the user, so just carry on. - if (!aPrincipal) - return NS_OK; - - bool hasCert; - aPrincipal->GetHasCertificate(&hasCert); - if (!hasCert) - return NS_ERROR_FAILURE; - - // See if the archive is signed at all first - nsCOMPtr<nsIPrincipal> principal; - nsresult rv = hZip->GetCertificatePrincipal(EmptyCString(), getter_AddRefs(principal)); - if (NS_FAILED(rv) || !principal) - return NS_ERROR_FAILURE; - - PRUint32 entryCount = 0; - - // first verify all files in the jar are also in the manifest. - nsCOMPtr<nsIUTF8StringEnumerator> entries; - rv = hZip->FindEntries(EmptyCString(), getter_AddRefs(entries)); - if (NS_FAILED(rv)) - return rv; - - bool more; - nsCAutoString name; - while (NS_SUCCEEDED(entries->HasMore(&more)) && more) - { - rv = entries->GetNext(name); - if (NS_FAILED(rv)) return rv; - - // Do not verify the directory entries or - // entries which are in the meta-inf directory - if ((name.Last() == '/') || - (PL_strncasecmp("META-INF/", name.get(), 9) == 0)) - continue; - - // Count the entries to be verified - entryCount++; - - // Each entry must be signed - rv = hZip->GetCertificatePrincipal(name, getter_AddRefs(principal)); - if (NS_FAILED(rv) || !principal) return NS_ERROR_FAILURE; - - bool equal; - rv = principal->Equals(aPrincipal, &equal); - if (NS_FAILED(rv) || !equal) return NS_ERROR_FAILURE; - } - - // next verify all files in the manifest are in the archive. - PRUint32 manifestEntryCount; - rv = hZip->GetManifestEntriesCount(&manifestEntryCount); - if (NS_FAILED(rv)) - return rv; - - if (entryCount != manifestEntryCount) - return NS_ERROR_FAILURE; // some files were deleted from archive - - return NS_OK; -} - - -/////////////////////////////////////////////////////////////////////////////////////////////// -// Function name : OpenAndValidateArchive -// Description : Opens install archive and validates contents -// Return type : PRInt32 -// Argument : nsIZipReader* hZip - the zip reader -// Argument : nsIFile* jarFile - the .xpi file -// Argument : nsIPrincipal* aPrincipal - a principal, if any, displayed to the user -// regarding the cert used to sign this install -/////////////////////////////////////////////////////////////////////////////////////////////// - -static PRInt32 -OpenAndValidateArchive(nsIZipReader* hZip, nsIFile* jarFile, nsIPrincipal* aPrincipal) -{ - if (!jarFile) - return nsInstall::DOWNLOAD_ERROR; - - nsCOMPtr<nsIFile> jFile; - nsresult rv =jarFile->Clone(getter_AddRefs(jFile)); - if (NS_SUCCEEDED(rv)) - rv = hZip->Open(jFile); - - if (NS_FAILED(rv)) - return nsInstall::CANT_READ_ARCHIVE; - - // CRC check the integrity of all items in this archive - rv = hZip->Test(EmptyCString()); - if (NS_FAILED(rv)) - { - NS_WARNING("CRC check of archive failed!"); - return nsInstall::CANT_READ_ARCHIVE; - } - - rv = VerifySigning(hZip, aPrincipal); - if (NS_FAILED(rv)) - { - NS_WARNING("Signing check of archive failed!"); - return nsInstall::INVALID_SIGNATURE; - } - - if (NS_FAILED(hZip->Test(nsDependentCString("install.rdf")))) - { - NS_WARNING("Archive did not contain an install manifest!"); - return nsInstall::NO_INSTALL_SCRIPT; - } - - return nsInstall::SUCCESS; -} - - -nsresult nsXPInstallManager::InstallItems() -{ - nsresult rv; - nsCOMPtr<nsIZipReader> hZip = do_CreateInstance(kZipReaderCID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr<nsIExtensionManager> em = do_GetService("@mozilla.org/extensions/manager;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); - - // can't cancel from here on cause we can't undo installs in a multitrigger - for (PRUint32 i = 0; i < mTriggers->Size(); ++i) - { - mItem = (nsXPITriggerItem*)mTriggers->Get(i); - if ( !mItem || !mItem->mFile ) - { - // notification for these errors already handled - continue; - } - - // If there was hash info in the trigger, but - // there wasn't a hash object created, then the - // algorithm used isn't known. - - if (mItem->mHashFound && !mItem->mHasher) - { - // report failure - mTriggers->SendStatus( mItem->mURL.get(), nsInstall::INVALID_HASH_TYPE ); - if (mDlg) - mDlg->OnStateChange( i, nsIXPIProgressDialog::INSTALL_DONE, - nsInstall::INVALID_HASH_TYPE ); - continue; - } - - // Don't install if we can't verify the hash (if specified) - if (mItem->mHasher && !VerifyHash(mItem)) - { - // report failure - mTriggers->SendStatus( mItem->mURL.get(), nsInstall::INVALID_HASH ); - if (mDlg) - mDlg->OnStateChange( i, nsIXPIProgressDialog::INSTALL_DONE, - nsInstall::INVALID_HASH ); - continue; - } - - if (mDlg) - mDlg->OnStateChange( i, nsIXPIProgressDialog::INSTALL_START, 0 ); - - PRInt32 finalStatus = OpenAndValidateArchive( hZip, - mItem->mFile, - mItem->mPrincipal); - hZip->Close(); - - if (finalStatus == nsInstall::SUCCESS) - { - rv = em->InstallItemFromFile( mItem->mFile, - NS_INSTALL_LOCATION_APPPROFILE); - if (NS_FAILED(rv)) - finalStatus = nsInstall::EXECUTION_ERROR; - } - - mTriggers->SendStatus( mItem->mURL.get(), finalStatus ); - if (mDlg) - mDlg->OnStateChange( i, nsIXPIProgressDialog::INSTALL_DONE, - finalStatus ); - } - return NS_OK; -} - -NS_IMETHODIMP nsXPInstallManager::DownloadNext() -{ - nsresult rv = NS_OK; - mContentLength = 0; - - if (mCancelled) - { - // Don't download any more if we were cancelled - Shutdown(); - return NS_OK; - } - - if ( mNextItem < mTriggers->Size() ) - { - //------------------------------------------------- - // There are items to download, get the next one - //------------------------------------------------- - mItem = (nsXPITriggerItem*)mTriggers->Get(mNextItem++); - - NS_ASSERTION( mItem, "bogus Trigger slipped through" ); - NS_ASSERTION( !mItem->mURL.IsEmpty(), "bogus trigger"); - if ( !mItem || mItem->mURL.IsEmpty() ) - { - // serious problem with trigger! Can't notify anyone of the - // error without the URL, just try to carry on. - return DownloadNext(); - } - - // --- Tell the dialog we're starting a download - if (mDlg) - mDlg->OnStateChange( mNextItem-1, nsIXPIProgressDialog::DOWNLOAD_START, 0 ); - - if ( mItem->IsFileURL() && mChromeType == NOT_CHROME ) - { - //-------------------------------------------------- - // Already local, we can open it where it is - //-------------------------------------------------- - nsCOMPtr<nsIURI> pURL; - rv = NS_NewURI(getter_AddRefs(pURL), mItem->mURL); - - if (NS_SUCCEEDED(rv)) - { - nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(pURL,&rv); - if (fileURL) - { - nsCOMPtr<nsIFile> localFile; - rv = fileURL->GetFile(getter_AddRefs(localFile)); - if (NS_SUCCEEDED(rv)) - { - mItem->mFile = do_QueryInterface(localFile,&rv); - } - } - } - - if ( NS_FAILED(rv) || !mItem->mFile ) - { - // send error status back - if (mDlg) - mDlg->OnStateChange( mNextItem-1, - nsIXPIProgressDialog::INSTALL_DONE, - nsInstall::UNEXPECTED_ERROR ); - mTriggers->SendStatus( mItem->mURL.get(), - nsInstall::UNEXPECTED_ERROR ); - mItem->mFile = 0; - } - else if (mDlg) - { - mDlg->OnStateChange( mNextItem-1, - nsIXPIProgressDialog::DOWNLOAD_DONE, 0); - } - - // --- on to the next one - return DownloadNext(); - } - else - { - //-------------------------------------------------- - // We have one to download - //-------------------------------------------------- - rv = GetDestinationFile(mItem->mURL, getter_AddRefs(mItem->mFile)); - if (NS_SUCCEEDED(rv)) - { - nsCOMPtr<nsIURI> pURL; - rv = NS_NewURI(getter_AddRefs(pURL), mItem->mURL); - if (NS_SUCCEEDED(rv)) - { - nsCOMPtr<nsIChannel> channel; - - rv = NS_NewChannel(getter_AddRefs(channel), pURL, nsnull, mLoadGroup, this); - if (NS_SUCCEEDED(rv)) - { - rv = channel->AsyncOpen(this, nsnull); - } - } - } - - if (NS_FAILED(rv)) - { - // announce failure - if (mDlg) - mDlg->OnStateChange( mNextItem-1, - nsIXPIProgressDialog::INSTALL_DONE, - nsInstall::DOWNLOAD_ERROR ); - mTriggers->SendStatus( mItem->mURL.get(), - nsInstall::DOWNLOAD_ERROR ); - mItem->mFile = 0; - - // We won't get Necko callbacks so start the next one now - return DownloadNext(); - } - } - } - else - { - //------------------------------------------------------ - // all downloaded, install them - //------------------------------------------------------ - InstallItems(); - Shutdown(); - } - - return rv; -} - - -//------------------------------------------------------------------- -// VerifyHash -// -// Returns true if the file hash matches the expected value (or if -// the item has no hash value). False if we can't verify the hash -// for any reason -// -bool nsXPInstallManager::VerifyHash(nsXPITriggerItem* aItem) -{ - NS_ASSERTION(aItem, "Null nsXPITriggerItem passed to VerifyHash"); - - nsresult rv; - if (!aItem->mHasher) - return false; - - nsCOMPtr<nsIInputStream> stream; - rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aItem->mFile); - if (NS_FAILED(rv)) return false; - - rv = aItem->mHasher->UpdateFromStream(stream, PR_UINT32_MAX); - if (NS_FAILED(rv)) return false; - - nsCAutoString binaryHash; - rv = aItem->mHasher->Finish(false, binaryHash); - if (NS_FAILED(rv)) return false; - - char* hash = nsnull; - for (PRUint32 i=0; i < binaryHash.Length(); ++i) - { - hash = PR_sprintf_append(hash,"%.2x", (PRUint8)binaryHash[i]); - } - - bool result = aItem->mHash.EqualsIgnoreCase(hash); - - PR_smprintf_free(hash); - return result; -} - - -void nsXPInstallManager::Shutdown(PRInt32 status) -{ - if (mDlg) - { - // tell the dialog it can go away - mDlg->OnStateChange(0, nsIXPIProgressDialog::DIALOG_CLOSE, 0 ); - mDlg = nsnull; - } - - if (mNeedsShutdown) - { - mNeedsShutdown = false; - - // Send remaining status notifications if we were cancelled early - nsXPITriggerItem* item; - while ( mNextItem < mTriggers->Size() ) - { - item = (nsXPITriggerItem*)mTriggers->Get(mNextItem++); - if ( item && !item->mURL.IsEmpty() ) - { - mTriggers->SendStatus( item->mURL.get(), status ); - } - } - - // Clean up downloaded files (regular install only, not chrome installs) - for (PRUint32 i = 0; i < mTriggers->Size(); i++ ) - { - item = static_cast<nsXPITriggerItem*>(mTriggers->Get(i)); - if ( item && item->mFile && !item->IsFileURL() ) - item->mFile->Remove(false); - } - - nsCOMPtr<nsIObserverService> os = - mozilla::services::GetObserverService(); - if (os) - { - os->RemoveObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC); - os->RemoveObserver(this, "quit-application"); - } - - if (mTriggers) - { - delete mTriggers; - mTriggers = nsnull; - } - - NS_RELEASE_THIS(); - } -} - -NS_IMETHODIMP -nsXPInstallManager::LoadParams(PRUint32 aCount, const PRUnichar** aPackageList, nsIDialogParamBlock** aParams) -{ - nsresult rv; - nsCOMPtr<nsIDialogParamBlock> paramBlock = do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &rv); - if (NS_SUCCEEDED(rv)) - { - // set OK and Cancel buttons - paramBlock->SetInt( 0, 2 ); - // pass in number of strings - paramBlock->SetInt( 1, aCount ); - // add strings - paramBlock->SetNumberStrings( aCount ); - for (PRUint32 i = 0; i < aCount; i++) - paramBlock->SetString( i, aPackageList[i] ); - } - - NS_IF_ADDREF(*aParams = paramBlock); - return rv; -} - - -NS_IMETHODIMP -nsXPInstallManager::GetDestinationFile(nsString& url, nsILocalFile* *file) -{ - NS_ENSURE_ARG_POINTER(file); - nsresult rv; - - nsCOMPtr<nsIProperties> directoryService = - do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsILocalFile> temp; - rv = directoryService->Get(NS_OS_TEMP_DIR, - NS_GET_IID(nsIFile), - getter_AddRefs(temp)); - NS_ENSURE_SUCCESS(rv, rv); - - temp->AppendNative(NS_LITERAL_CSTRING("tmp.xpi")); - temp->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); - *file = temp; - NS_IF_ADDREF(*file); - - return NS_OK; -} - -nsresult -nsXPInstallManager::CheckCert(nsIChannel* aChannel) -{ - nsCOMPtr<nsIURI> uri; - nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri)); - NS_ENSURE_SUCCESS(rv, rv); - nsCAutoString scheme; - rv = uri->GetScheme(scheme); - NS_ENSURE_SUCCESS(rv, rv); - if (!scheme.Equals(NS_LITERAL_CSTRING("https"))) - return NS_OK; - - nsCOMPtr<nsISupports> security; - rv = aChannel->GetSecurityInfo(getter_AddRefs(security)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr<nsISSLStatusProvider> statusProvider(do_QueryInterface(security)); - NS_ENSURE_TRUE(statusProvider, NS_ERROR_FAILURE); - - rv = statusProvider->GetSSLStatus(getter_AddRefs(security)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr<nsISSLStatus> status(do_QueryInterface(security)); - NS_ENSURE_TRUE(status, NS_ERROR_FAILURE); - nsCOMPtr<nsIX509Cert> cert; - rv = status->GetServerCert(getter_AddRefs(cert)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsIX509Cert> issuer; - rv = cert->GetIssuer(getter_AddRefs(issuer)); - NS_ENSURE_SUCCESS(rv, rv); - bool equal; - while (issuer && NS_SUCCEEDED(cert->Equals(issuer, &equal)) && !equal) { - cert = issuer; - rv = cert->GetIssuer(getter_AddRefs(issuer)); - NS_ENSURE_SUCCESS(rv, rv); - } - - if (issuer) { - PRUint32 length; - PRUnichar** tokenNames; - nsCOMPtr<nsIX509Cert3> issuer2(do_QueryInterface(issuer)); - NS_ENSURE_TRUE(status, NS_ERROR_FAILURE); - rv = issuer2->GetAllTokenNames(&length, &tokenNames); - NS_ENSURE_SUCCESS(rv ,rv); - for (PRUint32 i = 0; i < length; i++) { - if (nsDependentString(tokenNames[i]).Equals(NS_LITERAL_STRING("Builtin Object Token"))) - return NS_OK; - } - } - return NS_ERROR_FAILURE; -} - -NS_IMETHODIMP -nsXPInstallManager::OnStartRequest(nsIRequest* request, nsISupports *ctxt) -{ - nsresult rv = NS_ERROR_FAILURE; - - // If we are dealing with a HTTP request, then treat HTTP error pages as - // download failures. - nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(request); - if (httpChan) { - // If we were chrome lauched check the certificate on the request - if (mFromChrome && NS_FAILED(CheckCert(httpChan))) { - request->Cancel(NS_BINDING_ABORTED); - return NS_OK; - } - bool succeeded; - if (NS_SUCCEEDED(httpChan->GetRequestSucceeded(&succeeded)) && !succeeded) { - // HTTP response is not a 2xx! - request->Cancel(NS_BINDING_ABORTED); - return NS_OK; - } - } - - if (mLoadGroup) - mLoadGroup->RemoveRequest(request, nsnull, NS_BINDING_RETARGETED); - - NS_ASSERTION( mItem && mItem->mFile, "XPIMgr::OnStartRequest bad state"); - if ( mItem && mItem->mFile ) - { - NS_ASSERTION( !mItem->mOutStream, "Received double OnStartRequest from Necko"); - - rv = NS_NewLocalFileOutputStream(getter_AddRefs(mItem->mOutStream), - mItem->mFile, - PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, - 0600); - } - return rv; -} - - -NS_IMETHODIMP -nsXPInstallManager::OnStopRequest(nsIRequest *request, nsISupports *ctxt, - nsresult status) -{ - nsresult rv; - - switch( status ) - { - - case NS_BINDING_SUCCEEDED: - NS_ASSERTION( mItem->mOutStream, "XPIManager: output stream doesn't exist"); - rv = NS_OK; - break; - - case NS_BINDING_FAILED: - case NS_BINDING_ABORTED: - rv = status; - // XXX need to note failure, both to send back status - // to the callback, and also so we don't try to install - // this probably corrupt file. - break; - - default: - rv = NS_ERROR_ILLEGAL_VALUE; - } - - NS_ASSERTION( mItem, "Bad state in XPIManager"); - if ( mItem && mItem->mOutStream ) - { - mItem->mOutStream->Close(); - mItem->mOutStream = nsnull; - } - - if (NS_FAILED(rv) || mCancelled) - { - // Download error! - // -- first clean up partially downloaded file - if ( mItem && mItem->mFile ) - { - bool flagExists; - nsresult rv2 ; - rv2 = mItem->mFile->Exists(&flagExists); - if (NS_SUCCEEDED(rv2) && flagExists) - mItem->mFile->Remove(false); - - mItem->mFile = 0; - } - - // -- then notify interested parties - PRInt32 errorcode = mCancelled ? nsInstall::USER_CANCELLED - : nsInstall::DOWNLOAD_ERROR; - if (mDlg) - mDlg->OnStateChange( mNextItem-1, - nsIXPIProgressDialog::INSTALL_DONE, - errorcode ); - if (mItem) - mTriggers->SendStatus( mItem->mURL.get(), errorcode ); - } - else if (mDlg) - { - mDlg->OnStateChange( mNextItem-1, nsIXPIProgressDialog::DOWNLOAD_DONE, 0); - } - - DownloadNext(); - return rv; -} - - -NS_IMETHODIMP -nsXPInstallManager::OnDataAvailable(nsIRequest* request, nsISupports *ctxt, - nsIInputStream *pIStream, - PRUint32 sourceOffset, - PRUint32 length) -{ -#define XPI_ODA_BUFFER_SIZE 8*1024 - PRUint32 amt = NS_MIN(XPI_ODA_BUFFER_SIZE, length); - nsresult err; - char buffer[XPI_ODA_BUFFER_SIZE]; - PRUint32 writeCount; - - if (mCancelled) - { - // We must cancel this download in progress. We may get extra - // OnData calls if they were already queued so beware - request->Cancel(NS_BINDING_ABORTED); - return NS_ERROR_FAILURE; - } - - do - { - err = pIStream->Read(buffer, amt, &amt); - - if (amt == 0) break; - if (NS_FAILED(err)) return err; - - err = mItem->mOutStream->Write( buffer, amt, &writeCount); - if (NS_FAILED(err) || writeCount != amt) - { - return NS_ERROR_FAILURE; - } - length -= amt; - - amt = NS_MIN(XPI_ODA_BUFFER_SIZE, length); - - } while (length > 0); - - return NS_OK; -} - - -NS_IMETHODIMP -nsXPInstallManager::OnProgress(nsIRequest* request, nsISupports *ctxt, PRUint64 aProgress, PRUint64 aProgressMax) -{ - nsresult rv = NS_OK; - - if (mDlg && !mCancelled) - { - if (mContentLength < 1) { - nsCOMPtr<nsIChannel> channel = do_QueryInterface(request,&rv); - NS_ASSERTION(channel, "should have a channel"); - if (NS_FAILED(rv)) return rv; - rv = channel->GetContentLength(&mContentLength); - if (NS_FAILED(rv)) return rv; - } - // XXX once channels support that, use 64-bit contentlength - rv = mDlg->OnProgress( mNextItem-1, aProgress, PRUint64(mContentLength) ); - } - - return rv; -} - -NS_IMETHODIMP -nsXPInstallManager::OnStatus(nsIRequest* request, nsISupports *ctxt, - nsresult aStatus, const PRUnichar *aStatusArg) -{ - // don't need to do anything - return NS_OK; -} - -// nsIInterfaceRequestor method -NS_IMETHODIMP -nsXPInstallManager::GetInterface(const nsIID & eventSinkIID, void* *_retval) -{ - if (eventSinkIID.Equals(NS_GET_IID(nsIAuthPrompt))) { - *_retval = nsnull; - - nsresult rv; - nsCOMPtr<nsIWindowWatcher> ww(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsIAuthPrompt> prompt; - rv = ww->GetNewAuthPrompter(nsnull, getter_AddRefs(prompt)); - NS_ENSURE_SUCCESS(rv, rv); - - nsIAuthPrompt *p = prompt.get(); - NS_ADDREF(p); - *_retval = p; - return NS_OK; - } - else if (eventSinkIID.Equals(NS_GET_IID(nsIBadCertListener2))) { - // If we aren't chrome triggered fall back to the default dialogs - if (!mFromChrome) - return NS_ERROR_NO_INTERFACE; - } - return QueryInterface(eventSinkIID, (void**)_retval); -} - -// nsIChannelEventSink method -NS_IMETHODIMP -nsXPInstallManager::AsyncOnChannelRedirect(nsIChannel *oldChannel, - nsIChannel *newChannel, - PRUint32 flags, - nsIAsyncVerifyRedirectCallback *callback) -{ - // Chrome triggered installs need to have their certificates checked - if (mFromChrome) { - nsresult rv = CheckCert(oldChannel); - if (NS_FAILED(rv())) - return rv; - } - - callback->OnRedirectVerifyCallback(NS_OK); - return NS_OK; -} - -// nsIBadCertListener2 methods -NS_IMETHODIMP -nsXPInstallManager::NotifyCertProblem(nsIInterfaceRequestor *socketInfo, - nsISSLStatus *status, - const nsACString &targetSite, - bool *_retval) -{ - *_retval = true; - return NS_OK; -} - -// nsISSLErrorListener methods -NS_IMETHODIMP -nsXPInstallManager::NotifySSLError(nsIInterfaceRequestor *socketInfo, - PRInt32 error, - const nsACString &targetSite, - bool *_retval) -{ - *_retval = true; - return NS_OK; -} - -NS_IMETHODIMP -nsXPInstallManager::OnCertAvailable(nsIURI *aURI, - nsISupports* context, - nsresult aStatus, - nsIPrincipal *aPrincipal) -{ - if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED) { - // Check for a bad status. The only acceptable failure status code we accept - // is NS_BINDING_ABORTED. For all others we want to ensure that the - // nsIPrincipal is nsnull. - - NS_ASSERTION(aPrincipal == nsnull, "There has been an error, but we have a principal!"); - aPrincipal = nsnull; - } - - // get the current one and assign the cert name - nsXPITriggerItem *item = mTriggers->Get(mOutstandingCertLoads); - item->SetPrincipal(aPrincipal); - - if (mOutstandingCertLoads == 0) { - InitManagerInternal(); - return NS_OK; - } - - // get the next one to load. If there is any failure, we just go on to the - // next trigger. When all triggers items are handled, we call into InitManagerInternal - - item = mTriggers->Get(--mOutstandingCertLoads); - - nsCOMPtr<nsIURI> uri; - NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(item->mURL.get()).get()); - - if (!uri || mChromeType != NOT_CHROME) - return OnCertAvailable(uri, context, NS_ERROR_FAILURE, nsnull); - - nsIStreamListener* listener = new CertReader(uri, nsnull, this); - if (!listener) - return OnCertAvailable(uri, context, NS_ERROR_FAILURE, nsnull); - - NS_ADDREF(listener); - nsresult rv = NS_OpenURI(listener, nsnull, uri, nsnull, mLoadGroup); - - NS_ASSERTION(NS_SUCCEEDED(rv), "OpenURI failed"); - NS_RELEASE(listener); - - if (NS_FAILED(rv)) - return OnCertAvailable(uri, context, NS_ERROR_FAILURE, nsnull); - - return NS_OK; -} -
deleted file mode 100644 --- a/xpinstall/src/nsXPInstallManager.h +++ /dev/null @@ -1,144 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> - * Dave Townsend <dtownsend@oxymoronical.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef _NS_XPINSTALLMANAGER_H -#define _NS_XPINSTALLMANAGER_H - -#include "nsInstall.h" - -#include "nscore.h" -#include "nsISupports.h" -#include "nsString.h" - -#include "nsIURL.h" -#include "nsIInputStream.h" -#include "nsIStreamListener.h" -#include "nsIXPInstallManager.h" -#include "nsIXPIDialogService.h" -#include "nsXPITriggerInfo.h" -#include "nsIXPIProgressDialog.h" -#include "nsIChromeRegistry.h" -#include "nsIDOMWindow.h" -#include "nsIObserver.h" -#include "nsIBadCertListener2.h" -#include "nsISSLErrorListener.h" -#include "nsIChannelEventSink.h" -#include "nsIZipReader.h" -#include "nsIXPIInstallInfo.h" -#include "nsILoadGroup.h" - -#include "nsCOMPtr.h" - -#include "nsIProgressEventSink.h" -#include "nsIInterfaceRequestor.h" -#include "nsIInterfaceRequestorUtils.h" - -#include "nsIDialogParamBlock.h" - -#include "nsPICertNotification.h" - -#include "nsWeakReference.h" - -#define NS_XPIDIALOGSERVICE_CONTRACTID "@mozilla.org/embedui/xpinstall-dialog-service;1" -#define NS_XPINSTALLMANAGERCOMPONENT_CONTRACTID "@mozilla.org/xpinstall/install-manager;1" -#define XPI_PROGRESS_TOPIC "xpinstall-progress" - -class nsXPInstallManager : public nsIXPIDialogService, - public nsIXPInstallManager, - public nsIObserver, - public nsIStreamListener, - public nsIProgressEventSink, - public nsIInterfaceRequestor, - public nsPICertNotification, - public nsIBadCertListener2, - public nsISSLErrorListener, - public nsIChannelEventSink, - public nsSupportsWeakReference -{ - public: - nsXPInstallManager(); - virtual ~nsXPInstallManager(); - - NS_DECL_ISUPPORTS - NS_DECL_NSIXPIDIALOGSERVICE - NS_DECL_NSIXPINSTALLMANAGER - NS_DECL_NSIOBSERVER - NS_DECL_NSISTREAMLISTENER - NS_DECL_NSIPROGRESSEVENTSINK - NS_DECL_NSIREQUESTOBSERVER - NS_DECL_NSIINTERFACEREQUESTOR - NS_DECL_NSPICERTNOTIFICATION - NS_DECL_NSIBADCERTLISTENER2 - NS_DECL_NSISSLERRORLISTENER - NS_DECL_NSICHANNELEVENTSINK - - NS_IMETHOD InitManager(nsIDOMWindow* aParentWindow, nsXPITriggerInfo* aTrigger, PRUint32 aChromeType ); - - private: - nsresult InitManagerInternal(); - nsresult InstallItems(); - NS_IMETHOD DownloadNext(); - void Shutdown(PRInt32 status = nsInstall::USER_CANCELLED); - NS_IMETHOD GetDestinationFile(nsString& url, nsILocalFile* *file); - NS_IMETHOD LoadParams(PRUint32 aCount, const PRUnichar** aPackageList, nsIDialogParamBlock** aParams); -#ifdef ENABLE_SKIN_SIMPLE_INSTALLATION_UI - bool ConfirmChromeInstall(nsIDOMWindow* aParentWindow, const PRUnichar** aPackage); -#endif - bool VerifyHash(nsXPITriggerItem* aItem); - PRInt32 GetIndexFromURL(const PRUnichar* aUrl); - nsresult CheckCert(nsIChannel* aChannel); - - nsXPITriggerInfo* mTriggers; - nsXPITriggerItem* mItem; - PRUint32 mNextItem; - PRUint32 mChromeType; - PRInt32 mContentLength; - PRInt32 mOutstandingCertLoads; - bool mDialogOpen; - bool mCancelled; - bool mNeedsShutdown; - bool mFromChrome; - - nsCOMPtr<nsIXPIProgressDialog> mDlg; - - nsCOMPtr<nsIDOMWindow> mParentWindow; - nsCOMPtr<nsILoadGroup> mLoadGroup; -}; - -#endif
deleted file mode 100644 index ca333944cb5797e387b9dca07777ac1f1d73cf62..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 --- a/xpinstall/test/pre_checkin_trigger.html +++ /dev/null @@ -1,74 +0,0 @@ -<HTML> -<!-- ***** BEGIN LICENSE BLOCK ***** - - Version: MPL 1.1/GPL 2.0/LGPL 2.1 - - - - The contents of this file are subject to the Mozilla Public License Version - - 1.1 (the "License"); you may not use this file except in compliance with - - the License. You may obtain a copy of the License at - - http://www.mozilla.org/MPL/ - - - - Software distributed under the License is distributed on an "AS IS" basis, - - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - - for the specific language governing rights and limitations under the - - License. - - - - The Original Code is Mozilla Communicator client code, released March - - 31, 1998. - - - - The Initial Developer of the Original Code is - - Netscape Communications Corporation. - - Portions created by the Initial Developer are Copyright (C) 1998-1999 - - the Initial Developer. All Rights Reserved. - - - - Contributor(s): - - Samir Gehani <sgehani@netscape.com> - - - - Alternatively, the contents of this file may be used under the terms of - - either the GNU General Public License Version 2 or later (the "GPL"), or - - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - - in which case the provisions of the GPL or the LGPL are applicable instead - - of those above. If you wish to allow use of your version of this file only - - under the terms of either the GPL or the LGPL, and not to allow others to - - use your version of this file under the terms of the MPL, indicate your - - decision by deleting the provisions above and replace them with the notice - - and other provisions required by the LGPL or the GPL. If you do not delete - - the provisions above, a recipient may use your version of this file under - - the terms of any one of the MPL, the GPL or the LGPL. - - - - ***** END LICENSE BLOCK ***** --> -<HEAD> -<TITLE>XPInstall Pre-Checkin Trigger Test</TITLE> - -<SCRIPT> - -function xpinstallCallback(url, status) -{ - if (status == 0) - msg = "XPInstall Test: PASSED\n"; - else - msg = "XPInstall Test: FAILED\n"; - - dump(msg); - alert(msg); -} - -</SCRIPT> -</HEAD> - -<BODY> - -<H3>XPInstall Pre-Checkin Trigger Test</H3> -<HR> -Click on the link below to execute the XPInstall pre-checkin test. <BR> -The test result (PASS or FAIL) will pop up in an alert after the test completes. <P> - -<A HREF="javascript: - - xpi={'XPInstall Pre-Checkin Test':'pre_checkin.xpi'}; - InstallTrigger.install(xpi,xpinstallCallback); - -">Test XPInstall</A> - - -</BODY> -</HTML>
deleted file mode 100644 --- a/xpinstall/test/testXPIDialogService.js +++ /dev/null @@ -1,170 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla XPInstall. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 2002 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Daniel Veditz <dveditz@netscape.com> (Original Author) - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/* implementation of a test XPInstall Dialog Service */ - -// ----------------------------------------------------------------------- -// Test the XPInstall embedding API's by dropping this component into -// the Mozilla components directory and registering it. -// -// Do not export as part of a normal build since this will override the -// built-in Mozilla UI we want to use. -// ----------------------------------------------------------------------- - -// ----------------------------------------------------------------------- -// constants -// ----------------------------------------------------------------------- -const XPIDIALOGSERVICE_CONTRACTID = - "@mozilla.org/embedui/xpinstall-dialog-service;1"; - -const XPIDIALOGSERVICE_CID = - Components.ID("{9A5BEF68-3FDA-4926-9809-87A5A1CC8505}"); - -const XPI_TOPIC = "xpinstall-progress"; -const OPEN = "open"; -const CANCEL = "cancel"; - - -// ----------------------------------------------------------------------- -// XPInstall Dialog Service -// ----------------------------------------------------------------------- - -function testXPIDialogService() {} - -testXPIDialogService.prototype = -{ - QueryInterface: function( iid ) - { - if (iid.equals(Components.interfaces.nsIXPIDialogService) || - iid.equals(Components.interfaces.nsIXPIProgressDialog) || - iid.equals(Components.interfaces.nsISupports)) - return this; - - throw Components.results.NS_ERROR_NO_INTERFACE; - }, - - confirmInstall: function( parent, packages, count ) - { - // stash parent window for use later - this.mParent = parent; - - // quick and dirty data display - var str = "num packages: " + count/2 + "\n\n"; - for ( i = 0; i < count; ++i) - str += packages[i++] + ' -- ' + packages[i] + '\n'; - - str += "\nDo you want to install?"; - - return parent.confirm(str); - }, - - openProgressDialog: function( packages, count, mgr ) - { - this.dlg = this.mParent.open(); - mgr.observe( this, XPI_TOPIC, OPEN ); - }, - - onStateChange: function( index, state, error ) - { - dump("---XPIDlg--- State: "+index+', '+state+', '+error+'\n'); - }, - - onProgress: function( index, value, max ) - { - dump("---XPIDlg--- "+index+": "+value+' of '+max+'\n'); - } -}; - - - -// ----------------------------------------------------------------------- -// XPInstall Dialog Service Module and Factory -// ----------------------------------------------------------------------- - -// --- module entry point --- -function NSGetModule(compMgr, fileSpec) { return XPIDlgSvcModule; } - - -// --- module --- -var XPIDlgSvcModule = -{ - registerSelf: function( compMgr, fileSpec, location, type ) - { - compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); - - compMgr.registerFactoryLocation(XPIDIALOGSERVICE_CID, - 'XPInstall Dialog Service test component', - XPIDIALOGSERVICE_CONTRACTID, fileSpec, - location, type); - }, - - unregisterSelf: function( compMgr, fileSpec, location ) - { - compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); - compMgr.unregisterFactoryLocation(XPIDIALOGSERVICE_CID, fileSpec); - }, - - getClassObject: function( compMgr, cid, iid ) - { - if (!cid.equals(XPIDIALOGSERVICE_CID)) - throw Components.results.NS_ERROR_NO_INTERFACE; - - if (!iid.equals(Components.interfaces.nsIFactory)) - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - - return XPIDlgSvcFactory; - }, - - canUnload: function( compMgr ) { return true; } -}; - - -// --- factory --- -var XPIDlgSvcFactory = -{ - createInstance: function( outer, iid ) - { - if (outer != null) - throw Components.results.NS_ERROR_NO_AGGREGATION; - - if (!iid.equals(Components.interfaces.nsIXPIDialogService) && - !iid.equals(Components.interfaces.nsISupports)) - throw Components.results.NS_ERROR_INVALID_ARG; - - return new testXPIDialogService(); - } -};
deleted file mode 100644 --- a/xpinstall/tests/Makefile.in +++ /dev/null @@ -1,108 +0,0 @@ -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Mozilla Foundation. -# Portions created by the Initial Developer are Copyright (C) 2008 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# -# Alternatively, the contents of this file may be used under the terms of -# either of the GNU General Public License Version 2 or later (the "GPL"), -# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** - -DEPTH = ../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ -relativesrcdir = xpinstall/tests - -include $(DEPTH)/config/autoconf.mk -include $(topsrcdir)/config/rules.mk - -ifneq (mobile,$(MOZ_BUILD_APP)) -_BROWSER_FILES = harness.js \ - browser_unsigned_url.js \ - browser_unsigned_trigger.js \ - browser_whitelist.js \ - browser_whitelist2.js \ - browser_whitelist3.js \ - browser_whitelist4.js \ - browser_whitelist5.js \ - browser_whitelist6.js \ - browser_hash.js \ - browser_badhash.js \ - browser_badhashtype.js \ - browser_signed_url.js \ - browser_signed_trigger.js \ - browser_signed_untrusted.js \ - browser_signed_tampered.js \ - browser_signed_multiple.js \ - browser_signed_naming.js \ - browser_empty.js \ - browser_corrupt.js \ - browser_cookies.js \ - browser_cookies2.js \ - browser_cookies3.js \ - browser_cookies4.js \ - browser_enabled.js \ - browser_enabled2.js \ - browser_enabled3.js \ - browser_softwareupdate.js \ - browser_installchrome.js \ - browser_opendialog.js \ - browser_localfile.js \ - browser_localfile2.js \ - browser_auth.js \ - browser_auth2.js \ - browser_auth3.js \ - browser_offline.js \ - browser_chrome.js \ - browser_cancel.js \ - browser_navigateaway.js \ - browser_navigateaway2.js \ - browser_bug540558.js \ - unsigned.xpi \ - signed.xpi \ - signed2.xpi \ - signed-no-o.xpi \ - signed-no-cn.xpi \ - signed-untrusted.xpi \ - signed-tampered.xpi \ - empty.xpi \ - corrupt.xpi \ - enabled.html \ - installtrigger.html \ - startsoftwareupdate.html \ - installchrome.html \ - authRedirect.sjs \ - cookieRedirect.sjs \ - bug540558.html \ - $(NULL) - -libs:: $(_BROWSER_FILES) - $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) -endif
deleted file mode 100644 --- a/xpinstall/tests/authRedirect.sjs +++ /dev/null @@ -1,21 +0,0 @@ -// Simple script redirects to the query part of the uri if the browser -// authenticates with username "testuser" password "testpass" - -function handleRequest(request, response) { - if (request.hasHeader("Authorization")) { - if (request.getHeader("Authorization") == "Basic dGVzdHVzZXI6dGVzdHBhc3M=") { - response.setStatusLine(request.httpVersion, 302, "Found"); - response.setHeader("Location", request.queryString); - response.write("See " + request.queryString); - } - else { - response.setStatusLine(request.httpVersion, 403, "Forbidden"); - response.write("Invalid credentials"); - } - } - else { - response.setStatusLine(request.httpVersion, 401, "Authentication required"); - response.setHeader("WWW-Authenticate", "basic realm=\"XPInstall\"", false); - response.write("Unauthenticed request"); - } -}
deleted file mode 100644 --- a/xpinstall/tests/browser_auth.js +++ /dev/null @@ -1,49 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install succeeds when authentication is required -// This verifies bug 312473 -function test() { - Harness.authenticationCallback = get_auth_info; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "authRedirect.sjs?" + TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function get_auth_info() { - return [ "testuser", "testpass" ]; -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var authMgr = Components.classes['@mozilla.org/network/http-auth-manager;1'] - .getService(Components.interfaces.nsIHttpAuthManager); - authMgr.clearAll(); - - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_auth2.js +++ /dev/null @@ -1,45 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install fails when authentication is required and bad -// credentials are given -// This verifies bug 312473 -function test() { - Harness.authenticationCallback = get_auth_info; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "authRedirect.sjs?" + TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function get_auth_info() { - return [ "baduser", "badpass" ]; -} - -function check_xpi_install(addon, status) { - is(status, -228, "Install should fail"); -} - -function finish_test() { - var authMgr = Components.classes['@mozilla.org/network/http-auth-manager;1'] - .getService(Components.interfaces.nsIHttpAuthManager); - authMgr.clearAll(); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_auth3.js +++ /dev/null @@ -1,45 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install fails when authentication is required and it is -// canceled -// This verifies bug 312473 -function test() { - Harness.authenticationCallback = get_auth_info; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "authRedirect.sjs?" + TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function get_auth_info() { - return null; -} - -function check_xpi_install(addon, status) { - is(status, -228, "Install should fail"); -} - -function finish_test() { - var authMgr = Components.classes['@mozilla.org/network/http-auth-manager;1'] - .getService(Components.interfaces.nsIHttpAuthManager); - authMgr.clearAll(); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_badhash.js +++ /dev/null @@ -1,39 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install fails when an invalid hash is included -// This verifies bug 302284 -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": { - URL: TESTROOT + "unsigned.xpi", - Hash: "sha1:643b08418599ddbd1ea8a511c90696578fb844b9", - toString: function() { return this.URL; } - } - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, -261, "Install should fail"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_badhashtype.js +++ /dev/null @@ -1,39 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install fails when an unknown hash type is included -// This verifies bug 302284 -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": { - URL: TESTROOT + "unsigned.xpi", - Hash: "foo:3d0dc22e1f394e159b08aaf5f0f97de4d5c65f4f", - toString: function() { return this.URL; } - } - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, -261, "Install should fail"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_bug540558.js +++ /dev/null @@ -1,35 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that calling InstallTrigger.installChrome works -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "bug540558.html"); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_cancel.js +++ /dev/null @@ -1,47 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that cancelling an in progress download works. -var gManager = null; - -function test() { - waitForExplicitFinish(); - gManager = Components.classes["@mozilla.org/xpinstall/install-manager;1"] - .createInstance(Components.interfaces.nsIXPInstallManager); - gManager.initManagerFromChrome([ TESTROOT + "unsigned.xpi" ], - 1, listener); -} - -function finish_test() { - finish(); -} - -var listener = { - onStateChange: function(index, state, value) { - is(index, 0, "There is only one download"); - if (state == Components.interfaces.nsIXPIProgressDialog.INSTALL_DONE) - is(value, -210, "Install should have been cancelled"); - else if (state == Components.interfaces.nsIXPIProgressDialog.DIALOG_CLOSE) - finish_test(); - }, - - onProgress: function(index, value, maxValue) { - is(index, 0, "There is only one download"); - gManager.QueryInterface(Components.interfaces.nsIObserver); - gManager.observe(null, "xpinstall-progress", "cancel"); - }, - - QueryInterface: function(iid) { - if (iid.equals(Components.interfaces.nsIXPIProgressDialog) || - iid.equals(Components.interfaces.nsISupports)) - return this; - - throw Components.results.NS_ERROR_NO_INTERFACE; - } -}; -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_chrome.js +++ /dev/null @@ -1,47 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that starting a download from chrome works and bypasses the whitelist -function test() { - waitForExplicitFinish(); - var xpimgr = Components.classes["@mozilla.org/xpinstall/install-manager;1"] - .createInstance(Components.interfaces.nsIXPInstallManager); - xpimgr.initManagerFromChrome([ TESTROOT + "unsigned.xpi" ], - 1, listener); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - finish(); -} - -var listener = { - onStateChange: function(index, state, value) { - is(index, 0, "There is only one download"); - if (state == Components.interfaces.nsIXPIProgressDialog.INSTALL_DONE) - is(value, 0, "Install should have succeeded"); - else if (state == Components.interfaces.nsIXPIProgressDialog.DIALOG_CLOSE) - finish_test(); - }, - - onProgress: function(index, value, maxValue) { - is(index, 0, "There is only one download"); - }, - - QueryInterface: function(iid) { - if (iid.equals(Components.interfaces.nsIXPIProgressDialog) || - iid.equals(Components.interfaces.nsISupports)) - return this; - - throw Components.results.NS_ERROR_NO_INTERFACE; - } -}; -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_cookies.js +++ /dev/null @@ -1,37 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test that an install that requires cookies to be sent fails when no cookies -// are set -// This verifies bug 462739 -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Cookie check": TESTROOT + "cookieRedirect.sjs?" + TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, -228, "Install should fail"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_cookies2.js +++ /dev/null @@ -1,50 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test that an install that requires cookies to be sent succeeds when cookies -// are set -// This verifies bug 462739 -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var cm = Components.classes["@mozilla.org/cookiemanager;1"] - .getService(Components.interfaces.nsICookieManager2); - cm.add("example.com", "/browser/xpinstall/tests", "xpinstall", "true", false, - false, true, (Date.now() / 1000) + 60); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Cookie check": TESTROOT + "cookieRedirect.sjs?" + TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - var cm = Components.classes["@mozilla.org/cookiemanager;1"] - .getService(Components.interfaces.nsICookieManager2); - cm.remove("example.com", "xpinstall", "/browser/xpinstall/tests", false); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_cookies3.js +++ /dev/null @@ -1,53 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test that an install that requires cookies to be sent succeeds when cookies -// are set and third party cookies are disabled. -// This verifies bug 462739 -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var cm = Components.classes["@mozilla.org/cookiemanager;1"] - .getService(Components.interfaces.nsICookieManager2); - cm.add("example.com", "/browser/xpinstall/tests", "xpinstall", "true", false, - false, true, (Date.now() / 1000) + 60); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - Services.prefs.setIntPref("network.cookie.cookieBehavior", 1); - - var triggers = encodeURIComponent(JSON.stringify({ - "Cookie check": TESTROOT + "cookieRedirect.sjs?" + TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - var cm = Components.classes["@mozilla.org/cookiemanager;1"] - .getService(Components.interfaces.nsICookieManager2); - cm.remove("example.com", "xpinstall", "/browser/xpinstall/tests", false); - - Services.prefs.clearUserPref("network.cookie.cookieBehavior"); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_cookies4.js +++ /dev/null @@ -1,49 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test that an install that requires cookies to be sent fails when cookies -// are set and third party cookies are disabled and the request is to a third -// party. -// This verifies bug 462739 -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var cm = Components.classes["@mozilla.org/cookiemanager;1"] - .getService(Components.interfaces.nsICookieManager2); - cm.add("example.com", "/browser/xpinstall/tests", "xpinstall", "true", false, - false, true, (Date.now() / 1000) + 60); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - Services.prefs.setIntPref("network.cookie.cookieBehavior", 1); - - var triggers = encodeURIComponent(JSON.stringify({ - "Cookie check": TESTROOT2 + "cookieRedirect.sjs?" + TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, -228, "Install should fail"); -} - -function finish_test() { - var cm = Components.classes["@mozilla.org/cookiemanager;1"] - .getService(Components.interfaces.nsICookieManager2); - cm.remove("example.com", "xpinstall", "/browser/xpinstall/tests", false); - - Services.prefs.clearUserPref("network.cookie.cookieBehavior"); - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_corrupt.js +++ /dev/null @@ -1,37 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install fails when the xpi is corrupt. -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Corrupt XPI": TESTROOT + "corrupt.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, -207, "Install should fail"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - var doc = gBrowser.contentDocument; - is(doc.getElementById("status").textContent, "-207", "Callback should have seen the failure"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_empty.js +++ /dev/null @@ -1,34 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install fails when there is no install script present. -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Empty XPI": TESTROOT + "empty.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, -204, "Install should fail"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_enabled.js +++ /dev/null @@ -1,27 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an InstallTrigger.enabled is working -function test() { - waitForExplicitFinish(); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - // Allow the in-page load handler to run first - executeSoon(page_loaded); - }, true); - gBrowser.loadURI(TESTROOT + "enabled.html"); -} - -function page_loaded() { - var doc = gBrowser.contentDocument; - is(doc.getElementById("enabled").textContent, "true", "installTrigger should have been enabled"); - gBrowser.removeCurrentTab(); - finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_enabled2.js +++ /dev/null @@ -1,31 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an InstallTrigger.enabled is working -function test() { - waitForExplicitFinish(); - - Services.prefs.setBoolPref("xpinstall.enabled", false); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - // Allow the in-page load handler to run first - executeSoon(page_loaded); - }, true); - gBrowser.loadURI(TESTROOT + "enabled.html"); -} - -function page_loaded() { - Services.prefs.clearUserPref("xpinstall.enabled"); - - var doc = gBrowser.contentDocument; - is(doc.getElementById("enabled").textContent, "false", "installTrigger should have not been enabled"); - gBrowser.removeCurrentTab(); - finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_enabled3.js +++ /dev/null @@ -1,35 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an InstallTrigger.install call fails when xpinstall is disabled -function test() { - waitForExplicitFinish(); - - Services.prefs.setBoolPref("xpinstall.enabled", false); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - // Allow the in-page load handler to run first - executeSoon(page_loaded); - }, true); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function page_loaded() { - Services.prefs.clearUserPref("xpinstall.enabled"); - - var doc = gBrowser.contentDocument; - is(doc.getElementById("return").textContent, "false", "installTrigger should have not been enabled"); - gBrowser.removeCurrentTab(); - finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_hash.js +++ /dev/null @@ -1,43 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install succeeds when a valid hash is included -// This verifies bug 302284 -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": { - URL: TESTROOT + "unsigned.xpi", - Hash: "sha1:3d0dc22e1f394e159b08aaf5f0f97de4d5c65f4f", - toString: function() { return this.URL; } - } - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_installchrome.js +++ /dev/null @@ -1,35 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that calling InstallTrigger.installChrome works -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installchrome.html? " + encodeURIComponent(TESTROOT + "unsigned.xpi")); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_localfile.js +++ /dev/null @@ -1,35 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an local file works when loading the url -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"] - .getService(Components.interfaces.nsIChromeRegistry); - var path = cr.convertChromeURL(makeURI(CHROMEROOT + "unsigned.xpi")).spec; - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(path); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_localfile2.js +++ /dev/null @@ -1,36 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install fails if the url is a local file when requested from -// web content -function test() { - waitForExplicitFinish(); - - var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"] - .getService(Components.interfaces.nsIChromeRegistry); - var path = cr.convertChromeURL(makeURI(CHROMEROOT + "unsigned.xpi")).spec; - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": path - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - // Allow the in-page load handler to run first - executeSoon(page_loaded); - }, true); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function page_loaded() { - var doc = gBrowser.contentDocument; - is(doc.getElementById("return").textContent, "exception", "installTrigger should have failed"); - gBrowser.removeCurrentTab(); - finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_navigateaway.js +++ /dev/null @@ -1,45 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that navigating away from the initiating page during the install -// doesn't break the install. -// This verifies bug 473060 -function test() { - Harness.downloadProgressCallback = download_progress; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function download_progress(addon, value, maxValue) { - gBrowser.loadURI("about:blank"); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_navigateaway2.js +++ /dev/null @@ -1,44 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that closing the initiating page during the install doesn't break the -// install. -// This verifies bugs 473060 and 475347 -function test() { - Harness.downloadProgressCallback = download_progress; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function download_progress(addon, value, maxValue) { - gBrowser.removeCurrentTab(); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - Services.perms.remove("example.com", "install"); - - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_offline.js +++ /dev/null @@ -1,50 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that going offline cancels an in progress download. -function test() { - Harness.downloadProgressCallback = download_progress; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function download_progress(addon, value, maxValue) { - try { - Services.io.manageOfflineStatus = false; - Services.prefs.setBoolPref("browser.offline", true); - Services.io.offline = true; - } catch (ex) { - } -} - -function check_xpi_install(addon, status) { - is(status, -210, "Install should be cancelled"); -} - -function finish_test() { - try { - Services.prefs.setBoolPref("browser.offline", false); - Services.io.offline = false; - } catch (ex) { - } - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_opendialog.js +++ /dev/null @@ -1,41 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Test whether an install succeeds when the progress dialog is already open. -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - window.openDialog("chrome://mozapps/content/extensions/extensions.xul", "", - "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable"); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_signed_multiple.js +++ /dev/null @@ -1,56 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing two signed add-ons in the same trigger works. -// This verifies bug 453545 -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Signed XPI": TESTROOT + "signed.xpi", - "Signed XPI 2": TESTROOT + "signed2.xpi", - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 2, "Should be 2 items listed in the confirmation dialog"); - is(items[0].name, "Signed XPI", "Should have seen the name from the trigger list"); - is(items[0].url, TESTROOT + "signed.xpi", "Should have listed the correct url for the item"); - is(items[0].cert, "(Object Signer)", "Should have seen the signer"); - is(items[0].signed, "true", "Should have listed the item as signed"); - is(items[1].name, "Signed XPI 2", "Should have seen the name from the trigger list"); - is(items[1].url, TESTROOT + "signed2.xpi", "Should have listed the correct url for the item"); - is(items[1].cert, "(Object Signer)", "Should have seen the signer"); - is(items[1].signed, "true", "Should have listed the item as signed"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, 0, "Installs should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("signed-xpi@tests.mozilla.org"); - em.cancelInstallItem("signed-xpi2@tests.mozilla.org"); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_signed_naming.js +++ /dev/null @@ -1,63 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that the correct signer is presented for combinations of O and CN present. -// The signed files have (when present) O=Mozilla Testing, CN=Object Signer -// This verifies bug 372980 -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Signed XPI (O and CN)": TESTROOT + "signed.xpi", - "Signed XPI (CN)": TESTROOT + "signed-no-o.xpi", - "Signed XPI (O)": TESTROOT + "signed-no-cn.xpi", - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 3, "Should be 3 items listed in the confirmation dialog"); - is(items[0].name, "Signed XPI (O and CN)", "Should have seen the name from the trigger list"); - is(items[0].url, TESTROOT + "signed.xpi", "Should have listed the correct url for the item"); - is(items[0].cert, "(Object Signer)", "Should have seen the signer"); - is(items[0].signed, "true", "Should have listed the item as signed"); - is(items[1].name, "Signed XPI (CN)", "Should have seen the name from the trigger list"); - is(items[1].url, TESTROOT + "signed-no-o.xpi", "Should have listed the correct url for the item"); - is(items[1].cert, "(Object Signer)", "Should have seen the signer"); - is(items[1].signed, "true", "Should have listed the item as signed"); - is(items[2].name, "Signed XPI (O)", "Should have seen the name from the trigger list"); - is(items[2].url, TESTROOT + "signed-no-cn.xpi", "Should have listed the correct url for the item"); - is(items[2].cert, "(Mozilla Testing)", "Should have seen the signer"); - is(items[2].signed, "true", "Should have listed the item as signed"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, 0, "Installs should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("signed-xpi@tests.mozilla.org"); - em.cancelInstallItem("signed-xpi-no-o@tests.mozilla.org"); - em.cancelInstallItem("signed-xpi-no-cn@tests.mozilla.org"); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_signed_tampered.js +++ /dev/null @@ -1,45 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing a signed add-on that has been tampered with after signing. -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Tampered Signed XPI": TESTROOT + "signed-tampered.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 1, "Should only be 1 item listed in the confirmation dialog"); - is(items[0].name, "Tampered Signed XPI", "Should have seen the name from the trigger list"); - is(items[0].url, TESTROOT + "signed-tampered.xpi", "Should have listed the correct url for the item"); - is(items[0].cert, "(Object Signer)", "Should have seen the signer"); - is(items[0].signed, "true", "Should have listed the item as signed"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, -260, "Install should fail"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_signed_trigger.js +++ /dev/null @@ -1,50 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an signed add-on through an InstallTrigger call in web -// content. -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Signed XPI": TESTROOT + "signed.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 1, "Should only be 1 item listed in the confirmation dialog"); - is(items[0].name, "Signed XPI", "Should have seen the name from the trigger list"); - is(items[0].url, TESTROOT + "signed.xpi", "Should have listed the correct url for the item"); - is(items[0].cert, "(Object Signer)", "Should have seen the signer"); - is(items[0].signed, "true", "Should have listed the item as signed"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("signed-xpi@tests.mozilla.org"); - - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_signed_untrusted.js +++ /dev/null @@ -1,47 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an add-on signed by an untrusted certificate through an -// InstallTrigger call in web content. -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Untrusted Signed XPI": TESTROOT + "signed-untrusted.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 1, "Should only be 1 item listed in the confirmation dialog"); - is(items[0].name, "Untrusted Signed XPI", "Should have seen the name from the trigger list"); - is(items[0].url, TESTROOT + "signed-untrusted.xpi", "Should have listed the correct url for the item"); - is(items[0].cert, "(Unknown Signer)", "Should have seen the supposed signer"); - is(items[0].signed, "true", "Should have listed the item as signed"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, -260, "Install should fail"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_signed_url.js +++ /dev/null @@ -1,42 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an signed add-on by navigating directly to the url -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "signed.xpi"); -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 1, "Should only be 1 item listed in the confirmation dialog"); - is(items[0].name, "signed.xpi", "Should have had the filename for the item name"); - is(items[0].url, TESTROOT + "signed.xpi", "Should have listed the correct url for the item"); - is(items[0].cert, "(Object Signer)", "Should have seen the signer"); - is(items[0].signed, "true", "Should have listed the item as signed"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("signed-xpi@tests.mozilla.org"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_softwareupdate.js +++ /dev/null @@ -1,35 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests that calling InstallTrigger.startSoftwareUpdate works -function test() { - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "startsoftwareupdate.html? " + encodeURIComponent(TESTROOT + "unsigned.xpi")); -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_unsigned_trigger.js +++ /dev/null @@ -1,58 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an unsigned add-on through an InstallTrigger call in web -// content. -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": { - URL: TESTROOT + "unsigned.xpi", - IconURL: TESTROOT + "icon.png", - toString: function() { return this.URL; } - } - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 1, "Should only be 1 item listed in the confirmation dialog"); - is(items[0].name, "Unsigned XPI", "Should have seen the name from the trigger list"); - is(items[0].url, TESTROOT + "unsigned.xpi", "Should have listed the correct url for the item"); - is(items[0].icon, TESTROOT + "icon.png", "Should have listed the correct icon for the item"); - is(items[0].signed, "false", "Should have listed the item as unsigned"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - Services.perms.remove("example.com", "install"); - - var doc = gBrowser.contentDocument; - is(doc.getElementById("return").textContent, "true", "installTrigger should have claimed success"); - is(doc.getElementById("status").textContent, "0", "Callback should have seen a success"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_unsigned_url.js +++ /dev/null @@ -1,42 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an unsigned add-on by navigating directly to the url -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "unsigned.xpi"); -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 1, "Should only be 1 item listed in the confirmation dialog"); - is(items[0].name, "unsigned.xpi", "Should have had the filename for the item name"); - is(items[0].url, TESTROOT + "unsigned.xpi", "Should have listed the correct url for the item"); - is(items[0].icon, "", "Should have listed no icon for the item"); - is(items[0].signed, "false", "Should have listed the item as unsigned"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_whitelist.js +++ /dev/null @@ -1,53 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an unsigned add-on through an InstallTrigger call in web -// content. This should be blocked by the whitelist check. -// This verifies bug 252830 -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installBlockedCallback = allow_blocked; - Harness.installEndedCallback = check_xpi_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function allow_blocked(installInfo) { - is(installInfo.originatingWindow, gBrowser.contentWindow, "Install should have been triggered by the right window"); - is(installInfo.originatingURI.spec, gBrowser.currentURI.spec, "Install should have been triggered by the right uri"); - return true; -} - -function confirm_install(window) { - items = window.document.getElementById("itemList").childNodes; - is(items.length, 1, "Should only be 1 item listed in the confirmation dialog"); - is(items[0].name, "Unsigned XPI", "Should have seen the name from the trigger list"); - is(items[0].url, TESTROOT + "unsigned.xpi", "Should have listed the correct url for the item"); - is(items[0].signed, "false", "Should have listed the item as unsigned"); - return true; -} - -function check_xpi_install(addon, status) { - is(status, 0, "Install should succeed"); -} - -function finish_test() { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.cancelInstallItem("unsigned-xpi@tests.mozilla.org"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_whitelist2.js +++ /dev/null @@ -1,38 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an unsigned add-on through an InstallTrigger call in web -// content. This should be blocked by the whitelist check because the source -// is not whitelisted, even though the target is. -function test() { - Harness.installBlockedCallback = allow_blocked; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.org/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT2 + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); -} - -function allow_blocked(installInfo) { - is(installInfo.originatingWindow, gBrowser.contentWindow, "Install should have been triggered by the right window"); - is(installInfo.originatingURI.spec, gBrowser.currentURI.spec, "Install should have been triggered by the right uri"); - return false; -} - -function finish_test() { - Services.perms.remove("example.org", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_whitelist3.js +++ /dev/null @@ -1,35 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an unsigned add-on through a navigation. Should not be -// blocked since the referer is whitelisted. -function test() { - Harness.installConfirmCallback = confirm_install; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.org/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT2 + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "unsigned.xpi", makeURI(TESTROOT2 + "test.html")); -} - -function confirm_install(window) { - return false; -} - -function finish_test() { - Services.perms.remove("example.org", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_whitelist4.js +++ /dev/null @@ -1,37 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an unsigned add-on through a navigation. Should be -// blocked since the referer is not whitelisted even though the target is. -function test() { - Harness.installBlockedCallback = allow_blocked; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - var pm = Services.perms; - pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); - - var triggers = encodeURIComponent(JSON.stringify({ - "Unsigned XPI": TESTROOT2 + "unsigned.xpi" - })); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "unsigned.xpi", makeURI(TESTROOT2 + "test.html")); -} - -function allow_blocked(installInfo) { - is(installInfo.originatingWindow, gBrowser.contentWindow, "Install should have been triggered by the right window"); - is(installInfo.originatingURI.spec, TESTROOT2 + "test.html", "Install should have been triggered by the right uri"); - return false; -} - -function finish_test() { - Services.perms.remove("example.com", "install"); - - gBrowser.removeCurrentTab(); - Harness.finish(); -}
deleted file mode 100644 --- a/xpinstall/tests/browser_whitelist5.js +++ /dev/null @@ -1,31 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an unsigned add-on through a startSoftwareUpdate call in web -// content. This should be blocked by the whitelist check. -// This verifies bug 252830 -function test() { - Harness.installBlockedCallback = allow_blocked; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "startsoftwareupdate.html? " + encodeURIComponent(TESTROOT + "unsigned.xpi")); -} - -function allow_blocked(installInfo) { - is(installInfo.originatingWindow, gBrowser.contentWindow, "Install should have been triggered by the right window"); - is(installInfo.originatingURI.spec, gBrowser.currentURI.spec, "Install should have been triggered by the right uri"); - return false; -} - -function finish_test() { - gBrowser.removeCurrentTab(); - Harness.finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/browser_whitelist6.js +++ /dev/null @@ -1,31 +0,0 @@ -// Load in the test harness -var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - -var rootDir = getRootDirectory(window.location.href); -scriptLoader.loadSubScript(rootDir + "harness.js", this); - -// ---------------------------------------------------------------------------- -// Tests installing an unsigned add-on through an installChrome call in web -// content. This should be blocked by the whitelist check. -// This verifies bug 252830 -function test() { - Harness.installBlockedCallback = allow_blocked; - Harness.installsCompletedCallback = finish_test; - Harness.setup(); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.loadURI(TESTROOT + "installchrome.html? " + encodeURIComponent(TESTROOT + "unsigned.xpi")); -} - -function allow_blocked(installInfo) { - is(installInfo.originatingWindow, gBrowser.contentWindow, "Install should have been triggered by the right window"); - is(installInfo.originatingURI.spec, gBrowser.currentURI.spec, "Install should have been triggered by the right uri"); - return false; -} - -function finish_test() { - gBrowser.removeCurrentTab(); - Harness.finish(); -} -// ----------------------------------------------------------------------------
deleted file mode 100644 --- a/xpinstall/tests/bug540558.html +++ /dev/null @@ -1,23 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html> - -<!-- This page tests that window.InstallTrigger.install works --> - -<head> -<title>InstallTrigger tests</title> -<script type="text/javascript"> -function startInstall() { - window.InstallTrigger.install({ - "Unsigned XPI": "http://example.com/browser/xpinstall/tests/unsigned.xpi" - }); -} -</script> -</head> -<body onload="startInstall()"> -<p>InstallTrigger tests</p> -<p id="return"></p> -<p id="status"></p> -</body> -</html>
deleted file mode 100644 --- a/xpinstall/tests/cookieRedirect.sjs +++ /dev/null @@ -1,24 +0,0 @@ -// Simple script redirects to the query part of the uri if the cookie "xpinstall" -// has the value "true", otherwise gives a 500 error. - -function handleRequest(request, response) -{ - let cookie = null; - if (request.hasHeader("Cookie")) { - let cookies = request.getHeader("Cookie").split(";"); - for (let i = 0; i < cookies.length; i++) { - if (cookies[i].substring(0, 10) == "xpinstall=") - cookie = cookies[i].substring(10); - } - } - - if (cookie == "true") { - response.setStatusLine(request.httpVersion, 302, "Found"); - response.setHeader("Location", request.queryString); - response.write("See " + request.queryString); - } - else { - response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); - response.write("Invalid request"); - } -}
deleted file mode 100644 --- a/xpinstall/tests/corrupt.xpi +++ /dev/null @@ -1,1 +0,0 @@ -This is a corrupt zip file
deleted file mode 100644 index 74ed2b817426d02c0bf2313ed0f2ac728d354800..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 --- a/xpinstall/tests/enabled.html +++ /dev/null @@ -1,20 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html> - -<!-- This page will test if InstallTrigger seems to be enabled --> - -<head> -<title>InstallTrigger tests</title> -<script type="text/javascript"> -function init() { - document.getElementById("enabled").textContent = InstallTrigger.enabled() ? "true" : "false"; -} -</script> -</head> -<body onload="init()"> -<p>InstallTrigger tests</p> -<p id="enabled"></p> -</body> -</html>
deleted file mode 100644 --- a/xpinstall/tests/harness.js +++ /dev/null @@ -1,246 +0,0 @@ -const TESTROOT = "http://example.com/browser/xpinstall/tests/"; -const TESTROOT2 = "http://example.org/browser/xpinstall/tests/"; -const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; -const PROMPT_URL = "chrome://global/content/commonDialog.xul"; -const ADDONS_URL = "chrome://mozapps/content/extensions/extensions.xul"; - -var rootDir = getRootDirectory(gTestPath); -var path = rootDir.split('/'); -var chromeName = path[0] + '//' + path[2]; -var croot = chromeName + "/content/browser/xpinstall/tests/"; -var jar = getJar(croot); -if (jar) { - var tmpdir = extractJarToTmp(jar); - croot = 'file://' + tmpdir.path + '/'; -} -const CHROMEROOT = croot; - -/** - * This is a test harness designed to handle responding to UI during the process - * of installing an XPI. A test can set callbacks to hear about specific parts - * of the sequence. - * Before use setup must be called and finish must be called afterwards. - */ -var Harness = { - // If set then the install is expected to be blocked by the whitelist. The - // callback should return true to continue with the install anyway. - installBlockedCallback: null, - // If set will be called in the event of authentication being needed to get - // the xpi. Should return a 2 element array of username and password, or - // null to not authenticate. - authenticationCallback: null, - // If set this will be called to allow checking the contents of the xpinstall - // confirmation dialog. The callback should return true to continue the install. - installConfirmCallback: null, - // If set will be called when downloading of an item has begun. - downloadStartedCallback: null, - // If set will be called during the download of an item. - downloadProgressCallback: null, - // If set will be called when downloading of an item has ended. - downloadEndedCallback: null, - // If set will be called when installation by the extension manager of an xpi - // item starts - installStartedCallback: null, - // If set will be called when each xpi item to be installed completes - // installation. - installEndedCallback: null, - // If set will be called when all triggered items are installed or the install - // is canceled. - installsCompletedCallback: null, - - listenerIndex: null, - - // Setup and tear down functions - setup: function() { - waitForExplicitFinish(); - - var os = Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - os.addObserver(this, "xpinstall-install-blocked", false); - - var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] - .getService(Components.interfaces.nsIWindowMediator); - wm.addListener(this); - - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - this.listenerIndex = em.addInstallListener(this); - }, - - finish: function() { - var os = Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - os.removeObserver(this, "xpinstall-install-blocked"); - - var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] - .getService(Components.interfaces.nsIWindowMediator); - wm.removeListener(this); - var win = wm.getMostRecentWindow("Extension:Manager"); - if (win) - win.close(); - - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.removeInstallListenerAt(this.listenerIndex); - finish(); - }, - - endTest: function() { - // Defer the final notification to allow things like the InstallTrigger - // callback to complete - executeSoon(this.installsCompletedCallback); - - this.installBlockedCallback = null; - this.authenticationCallback = null; - this.installConfirmCallback = null; - this.downloadStartedCallback = null; - this.downloadProgressCallback = null; - this.downloadEndedCallback = null; - this.installStartedCallback = null; - this.installEndedCallback = null; - this.installsCompletedCallback = null; - }, - - // Window open handling - windowLoad: function(window) { - // Allow any other load handlers to execute - var self = this; - executeSoon(function() { self.windowReady(window); } ); - }, - - windowReady: function(window) { - if (window.document.location.href == XPINSTALL_URL) { - if (this.installBlockedCallback) - ok(false, "Should have been blocked by the whitelist"); - - // If there is a confirm callback then its return status determines whether - // to install the items or not. If not the test is over. - if (this.installConfirmCallback && !this.installConfirmCallback(window)) { - window.document.documentElement.cancelDialog(); - this.endTest(); - } - else { - // Initially the accept button is disabled on a countdown timer - var button = window.document.documentElement.getButton("accept"); - button.disabled = false; - window.document.documentElement.acceptDialog(); - } - } - else if (window.document.location.href == PROMPT_URL) { - switch (window.args.promptType) { - default: - if (window.opener.document.location.href == ADDONS_URL) { - // A prompt opened by the add-ons manager is liable to be an - // xpinstall error, just close it, we'll see the error in - // onInstallEnded anyway. - window.document.documentElement.acceptDialog(); - } - break; - case "promptUserAndPass": - // This is a login dialog, hopefully an authentication prompt - // for the xpi. - if (this.authenticationCallback) { - var auth = this.authenticationCallback(); - if (auth && auth.length == 2) { - window.document.getElementById("loginTextbox").value = auth[0]; - window.document.getElementById("password1Textbox").value = auth[1]; - window.document.documentElement.acceptDialog(); - } - else { - window.document.documentElement.cancelDialog(); - } - } - else { - window.document.documentElement.cancelDialog(); - } - break; - } - } - }, - - // Install blocked handling - - installBlocked: function(installInfo) { - ok(!!this.installBlockedCallback, "Shouldn't have been blocked by the whitelist"); - if (this.installBlockedCallback && this.installBlockedCallback(installInfo)) { - this.installBlockedCallback = null; - var mgr = Components.classes["@mozilla.org/xpinstall/install-manager;1"] - .createInstance(Components.interfaces.nsIXPInstallManager); - mgr.initManagerWithInstallInfo(installInfo); - } - else { - this.endTest(); - } - }, - - // nsIWindowMediatorListener - - onWindowTitleChange: function(window, title) { - }, - - onOpenWindow: function(window) { - var domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) - .getInterface(Components.interfaces.nsIDOMWindow); - var self = this; - domwindow.addEventListener("load", function() { - self.windowLoad(domwindow); - }, false); - }, - - onCloseWindow: function(window) { - }, - - // nsIAddonInstallListener - - onDownloadStarted: function(addon) { - if (this.downloadStartedCallback) - this.downloadStartedCallback(addon); - }, - - onDownloadProgress: function(addon, value, maxValue) { - if (this.downloadProgressCallback) - this.downloadProgressCallback(addon, value, maxValue); - }, - - onDownloadEnded: function(addon) { - if (this.downloadEndedCallback) - this.downloadEndedCallback(addon); - }, - - onInstallStarted: function(addon) { - if (this.installStartedCallback) - this.installStartedCallback(addon); - }, - - onCompatibilityCheckStarted: function(addon) { - }, - - onCompatibilityCheckEnded: function(addon, status) { - }, - - onInstallEnded: function(addon, status) { - if (this.installEndedCallback) - this.installEndedCallback(addon, status); - }, - - onInstallsCompleted: function() { - this.endTest(); - }, - - // nsIObserver - - observe: function(subject, topic, data) { - var installInfo = subject.QueryInterface(Components.interfaces.nsIXPIInstallInfo); - this.installBlocked(installInfo); - }, - - QueryInterface: function(iid) { - if (iid.equals(Components.interfaces.nsIObserver) || - iid.equals(Components.interfaces.nsIAddonInstallListener) || - iid.equals(Components.interfaces.nsIWindowMediatorListener) || - iid.equals(Components.interfaces.nsISupports)) - return this; - - throw Components.results.NS_ERROR_NO_INTERFACE; - } -}
deleted file mode 100644 --- a/xpinstall/tests/installchrome.html +++ /dev/null @@ -1,21 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html> - -<!-- This page will accept a url as the uri query and pass it to InstallTrigger.installChrome --> - -<head> -<title>InstallTrigger tests</title> -<script type="text/javascript"> -function startInstall() { - InstallTrigger.installChrome(InstallTrigger.SKIN, - decodeURIComponent(document.location.search.substring(1)), - "test"); -} -</script> -</head> -<body onload="startInstall()"> -<p>InstallTrigger tests</p> -</body> -</html>
deleted file mode 100644 --- a/xpinstall/tests/installtrigger.html +++ /dev/null @@ -1,32 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html> - -<!-- This page will accept some json as the uri query and pass it to InstallTrigger.install --> - -<head> -<title>InstallTrigger tests</title> -<script type="text/javascript"> -function installCallback(url, status) { - document.getElementById("status").textContent = status; -} - -function startInstall() { - var text = decodeURIComponent(document.location.search.substring(1)); - var triggers = JSON.parse(text); - try { - document.getElementById("return").textContent = InstallTrigger.install(triggers, installCallback); - } - catch (e) { - document.getElementById("return").textContent = "exception"; - } -} -</script> -</head> -<body onload="startInstall()"> -<p>InstallTrigger tests</p> -<p id="return"></p> -<p id="status"></p> -</body> -</html>
deleted file mode 100644 index 90d3a3ce66692496ee625b2ae1725785193079a5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index 19b754038008ed3043a7178aa8c42da83747bf8e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index 8c951881e5fbe10504eee8280dd9a3d995b5c881..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index 09789d1897ccd3c6e0d30c94dc658f5cda732771..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index bd7f78b7c7712c0aca18776e08541e87898da119..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index 085efbbf7fdd8f9cf475e903b16552e614f9139c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 --- a/xpinstall/tests/startsoftwareupdate.html +++ /dev/null @@ -1,19 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html> - -<!-- This page will accept a url as the uri query and pass it to InstallTrigger.startSoftwareUpdate --> - -<head> -<title>InstallTrigger tests</title> -<script type="text/javascript"> -function startInstall() { - InstallTrigger.startSoftwareUpdate(decodeURIComponent(document.location.search.substring(1))); -} -</script> -</head> -<body onload="startInstall()"> -<p>InstallTrigger tests</p> -</body> -</html>