diff options
Diffstat (limited to 'comm/mail/components/search/content/WinSearchIntegration.js')
-rw-r--r-- | comm/mail/components/search/content/WinSearchIntegration.js | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/comm/mail/components/search/content/WinSearchIntegration.js b/comm/mail/components/search/content/WinSearchIntegration.js new file mode 100644 index 0000000000..2f4c51b0e3 --- /dev/null +++ b/comm/mail/components/search/content/WinSearchIntegration.js @@ -0,0 +1,346 @@ +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// SearchIntegration.jsm +/* globals SearchIntegration, SearchSupport, Services */ + +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +var MSG_DB_LARGE_COMMIT = 1; +var CRLF = "\r\n"; + +/** + * Required to access the 64-bit registry, even though we're probably a 32-bit + * program + */ +var ACCESS_WOW64_64KEY = 0x0100; + +/** + * The contract ID for the helper service. + */ +var WINSEARCHHELPER_CONTRACTID = "@mozilla.org/mail/windows-search-helper;1"; + +/** + * All the registry keys required for integration + */ +var gRegKeys = [ + // This is the property handler + { + root: Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + key: "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers\\.wdseml", + name: "", + value: "{5FA29220-36A1-40f9-89C6-F4B384B7642E}", + }, + // These two are the association with the MIME IFilter + { + root: Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT, + key: ".wdseml", + name: "Content Type", + value: "message/rfc822", + }, + { + root: Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT, + key: ".wdseml\\PersistentHandler", + name: "", + value: "{5645c8c4-e277-11cf-8fda-00aa00a14f93}", + }, + // This is the association with the Windows mail preview handler + { + root: Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT, + key: ".wdseml\\shellex\\{8895B1C6-B41F-4C1C-A562-0D564250836F}", + name: "", + value: "{b9815375-5d7f-4ce2-9245-c9d4da436930}", + }, + // This is the association made to display results under email + { + root: Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + key: "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\KindMap", + name: ".wdseml", + value: "email;communication", + }, +]; + +/** + * @namespace Windows Search-specific desktop search integration functionality + */ +// eslint-disable-next-line no-global-assign +SearchIntegration = { + __proto__: SearchSupport, + + // The property of the header and (sometimes) folders that's used to check + // if a message is indexed + _hdrIndexedProperty: "winsearch_reindex_time", + + // The file extension that is used for support files of this component + _fileExt: ".wdseml", + + // The Windows Search pref base + _prefBase: "mail.winsearch.", + + // Helper (native) component + __winSearchHelper: null, + get _winSearchHelper() { + if (!this.__winSearchHelper) { + this.__winSearchHelper = Cc[WINSEARCHHELPER_CONTRACTID].getService( + Ci.nsIMailWinSearchHelper + ); + } + return this.__winSearchHelper; + }, + + // Whether the folders are already in the crawl scope + get _foldersInCrawlScope() { + return this._winSearchHelper.foldersInCrawlScope; + }, + + /** + * Whether all the required registry keys are present + * We'll be optimistic here and assume that once the registry keys have been + * added, they won't be removed, at least while Thunderbird is open + */ + __regKeysPresent: false, + get _regKeysPresent() { + if (!this.__regKeysPresent) { + for (let i = 0; i < gRegKeys.length; i++) { + let regKey = Cc["@mozilla.org/windows-registry-key;1"].createInstance( + Ci.nsIWindowsRegKey + ); + try { + regKey.open( + gRegKeys[i].root, + gRegKeys[i].key, + regKey.ACCESS_READ | ACCESS_WOW64_64KEY + ); + } catch (e) { + return false; + } + let valuePresent = + regKey.hasValue(gRegKeys[i].name) && + regKey.readStringValue(gRegKeys[i].name) == gRegKeys[i].value; + regKey.close(); + if (!valuePresent) { + return false; + } + } + this.__regKeysPresent = true; + } + return true; + }, + + // Use the folder's path (i.e., in profile dir) as is + _getSearchPathForFolder(aFolder) { + return aFolder.filePath; + }, + + // Use the search path as is + _getFolderForSearchPath(aDir) { + return MailUtils.getFolderForFileInProfile(aDir); + }, + + _pathNeedsReindexing(aPath) { + // only needed on MacOSX (see bug 670566). + return false; + }, + + _init() { + this._initLogging(); + // If the helper service isn't present, we weren't compiled with the needed + // support. Mark ourselves null and return + if (!(WINSEARCHHELPER_CONTRACTID in Cc)) { + SearchIntegration = null; // eslint-disable-line no-global-assign + return; + } + + // The search module is currently only enabled on Vista and above, + // and the app can only be installed on Windows 7 and above. + this.osVersionTooLow = false; + + let serviceRunning = false; + try { + serviceRunning = this._winSearchHelper.serviceRunning; + } catch (e) {} + // If the service isn't running, then we should stay in backoff mode + if (!serviceRunning) { + this._log.info("Windows Search service not running"); + this.osComponentsNotRunning = true; + this._initSupport(false); + return; + } + + let enabled = this.prefEnabled; + + if (enabled) { + this._log.info("Initializing Windows Search integration"); + } + this._initSupport(enabled); + }, + + /** + * Add necessary hooks to Windows + * + * @returns false if registration did not succeed, because the elevation + * request was denied + */ + register() { + // If any of the two are not present, we need to elevate. + if (!this._foldersInCrawlScope || !this._regKeysPresent) { + try { + this._winSearchHelper.runSetup(true); + } catch (e) { + return false; + } + } + + if (!this._winSearchHelper.isFileAssociationSet) { + try { + this._winSearchHelper.setFileAssociation(); + } catch (e) { + this._log.warn("File association not set"); + } + } + // Also set the FANCI bit to 0 for the profile directory + let profD = Services.dirsvc.get("ProfD", Ci.nsIFile); + this._winSearchHelper.setFANCIBit(profD, false, true); + + return true; + }, + + /** + * Remove integration from Windows. The only thing removed is the directory + * from the index list. This will ask for elevation. + * + * @returns false if deregistration did not succeed, because the elevation + * request was denied + */ + deregister() { + try { + this._winSearchHelper.runSetup(false); + } catch (e) { + return false; + } + + return true; + }, + + // The stream listener to read messages + _streamListener: { + __proto__: SearchSupport._streamListenerBase, + + // Buffer to store the message + _message: "", + + onStartRequest(request) { + try { + let outputFileStream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + outputFileStream.init(this._outputFile, -1, -1, 0); + this._outputStream = Cc[ + "@mozilla.org/intl/converter-output-stream;1" + ].createInstance(Ci.nsIConverterOutputStream); + this._outputStream.init(outputFileStream, "UTF-8"); + } catch (ex) { + this._onDoneStreaming(false); + } + }, + + onStopRequest(request, status) { + try { + // XXX Once the JS emitter gets checked in, this code should probably be + // switched over to use that + // Decode using getMsgTextFromStream + let stringStream = Cc[ + "@mozilla.org/io/string-input-stream;1" + ].createInstance(Ci.nsIStringInputStream); + stringStream.setData(this._message, this._message.length); + let contentType = {}; + let folder = this._msgHdr.folder; + let text = folder.getMsgTextFromStream( + stringStream, + this._msgHdr.charset, + 65536, + 50000, + false, + false, + contentType + ); + + // To get the Received header, we need to parse the message headers. + // We only need the first header, which contains the latest received + // date + let headers = this._message.split(/\r\n\r\n|\r\r|\n\n/, 1)[0]; + let mimeHeaders = Cc[ + "@mozilla.org/messenger/mimeheaders;1" + ].createInstance(Ci.nsIMimeHeaders); + mimeHeaders.initialize(headers); + let receivedHeader = mimeHeaders.extractHeader("Received", false); + + this._outputStream.writeString("From: " + this._msgHdr.author + CRLF); + // If we're a newsgroup, then add the name of the folder as the + // newsgroups header + if (folder instanceof Ci.nsIMsgNewsFolder) { + this._outputStream.writeString("Newsgroups: " + folder.name + CRLF); + } else { + this._outputStream.writeString( + "To: " + this._msgHdr.recipients + CRLF + ); + } + this._outputStream.writeString("CC: " + this._msgHdr.ccList + CRLF); + this._outputStream.writeString( + "Subject: " + this._msgHdr.subject + CRLF + ); + if (receivedHeader) { + this._outputStream.writeString("Received: " + receivedHeader + CRLF); + } + this._outputStream.writeString( + "Date: " + new Date(this._msgHdr.date / 1000).toUTCString() + CRLF + ); + this._outputStream.writeString( + "Content-Type: " + contentType.value + "; charset=utf-8" + CRLF + CRLF + ); + + this._outputStream.writeString(text + CRLF + CRLF); + + this._msgHdr.setUint32Property( + SearchIntegration._hdrIndexedProperty, + this._reindexTime + ); + folder.msgDatabase.commit(MSG_DB_LARGE_COMMIT); + + this._message = ""; + SearchIntegration._log.info("Successfully written file"); + } catch (ex) { + SearchIntegration._log.error(ex); + this._onDoneStreaming(false); + return; + } + this._onDoneStreaming(true); + }, + + onDataAvailable(request, inputStream, offset, count) { + try { + let inStream = Cc[ + "@mozilla.org/scriptableinputstream;1" + ].createInstance(Ci.nsIScriptableInputStream); + inStream.init(inputStream); + + // It is necessary to read in data from the input stream + let inData = inStream.read(count); + + // Ignore stuff after the first 50K or so + if (this._message && this._message.length > 50000) { + return; + } + + this._message += inData; + } catch (ex) { + SearchIntegration._log.error(ex); + this._onDoneStreaming(false); + } + }, + }, +}; + +SearchIntegration._init(); |