diff options
Diffstat (limited to 'toolkit/crashreporter/CrashSubmit.jsm')
-rw-r--r-- | toolkit/crashreporter/CrashSubmit.jsm | 570 |
1 files changed, 0 insertions, 570 deletions
diff --git a/toolkit/crashreporter/CrashSubmit.jsm b/toolkit/crashreporter/CrashSubmit.jsm deleted file mode 100644 index 76eafbdad..000000000 --- a/toolkit/crashreporter/CrashSubmit.jsm +++ /dev/null @@ -1,570 +0,0 @@ -/* 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/. */ - -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/KeyValueParser.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.importGlobalProperties(['File']); - -XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils", - "resource://gre/modules/PromiseUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -this.EXPORTED_SYMBOLS = [ - "CrashSubmit" -]; - -const STATE_START = Ci.nsIWebProgressListener.STATE_START; -const STATE_STOP = Ci.nsIWebProgressListener.STATE_STOP; - -const SUCCESS = "success"; -const FAILED = "failed"; -const SUBMITTING = "submitting"; - -const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - -function parseINIStrings(file) { - var factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. - getService(Ci.nsIINIParserFactory); - var parser = factory.createINIParser(file); - var obj = {}; - var en = parser.getKeys("Strings"); - while (en.hasMore()) { - var key = en.getNext(); - obj[key] = parser.getString("Strings", key); - } - return obj; -} - -// Since we're basically re-implementing part of the crashreporter -// client here, we'll just steal the strings we need from crashreporter.ini -function getL10nStrings() { - let dirSvc = Cc["@mozilla.org/file/directory_service;1"]. - getService(Ci.nsIProperties); - let path = dirSvc.get("GreD", Ci.nsIFile); - path.append("crashreporter.ini"); - if (!path.exists()) { - // see if we're on a mac - path = path.parent; - path = path.parent; - path.append("MacOS"); - path.append("crashreporter.app"); - path.append("Contents"); - path.append("Resources"); - path.append("crashreporter.ini"); - if (!path.exists()) { - // very bad, but I don't know how to recover - return null; - } - } - let crstrings = parseINIStrings(path); - let strings = { - 'crashid': crstrings.CrashID, - 'reporturl': crstrings.CrashDetailsURL - }; - - path = dirSvc.get("XCurProcD", Ci.nsIFile); - path.append("crashreporter-override.ini"); - if (path.exists()) { - crstrings = parseINIStrings(path); - if ('CrashID' in crstrings) - strings['crashid'] = crstrings.CrashID; - if ('CrashDetailsURL' in crstrings) - strings['reporturl'] = crstrings.CrashDetailsURL; - } - return strings; -} - -XPCOMUtils.defineLazyGetter(this, "strings", getL10nStrings); - -function getDir(name) { - let directoryService = Cc["@mozilla.org/file/directory_service;1"]. - getService(Ci.nsIProperties); - let dir = directoryService.get("UAppData", Ci.nsIFile); - dir.append("Crash Reports"); - dir.append(name); - return dir; -} - -function writeFile(dirName, fileName, data) { - let path = getDir(dirName); - if (!path.exists()) - path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0700', 8)); - path.append(fileName); - var fs = Cc["@mozilla.org/network/file-output-stream;1"]. - createInstance(Ci.nsIFileOutputStream); - // open, write, truncate - fs.init(path, -1, -1, 0); - var os = Cc["@mozilla.org/intl/converter-output-stream;1"]. - createInstance(Ci.nsIConverterOutputStream); - os.init(fs, "UTF-8", 0, 0x0000); - os.writeString(data); - os.close(); - fs.close(); -} - -function getPendingMinidump(id) { - let pendingDir = getDir("pending"); - let dump = pendingDir.clone(); - let extra = pendingDir.clone(); - let memory = pendingDir.clone(); - dump.append(id + ".dmp"); - extra.append(id + ".extra"); - memory.append(id + ".memory.json.gz"); - return [dump, extra, memory]; -} - -function getAllPendingMinidumpsIDs() { - let minidumps = []; - let pendingDir = getDir("pending"); - - if (!(pendingDir.exists() && pendingDir.isDirectory())) - return []; - let entries = pendingDir.directoryEntries; - - while (entries.hasMoreElements()) { - let entry = entries.getNext().QueryInterface(Ci.nsIFile); - if (entry.isFile()) { - let matches = entry.leafName.match(/(.+)\.extra$/); - if (matches) - minidumps.push(matches[1]); - } - } - - return minidumps; -} - -function pruneSavedDumps() { - const KEEP = 10; - - let pendingDir = getDir("pending"); - if (!(pendingDir.exists() && pendingDir.isDirectory())) - return; - let entries = pendingDir.directoryEntries; - let entriesArray = []; - - while (entries.hasMoreElements()) { - let entry = entries.getNext().QueryInterface(Ci.nsIFile); - if (entry.isFile()) { - let matches = entry.leafName.match(/(.+)\.extra$/); - if (matches) - entriesArray.push(entry); - } - } - - entriesArray.sort(function(a, b) { - let dateA = a.lastModifiedTime; - let dateB = b.lastModifiedTime; - if (dateA < dateB) - return -1; - if (dateB < dateA) - return 1; - return 0; - }); - - if (entriesArray.length > KEEP) { - for (let i = 0; i < entriesArray.length - KEEP; ++i) { - let extra = entriesArray[i]; - let matches = extra.leafName.match(/(.+)\.extra$/); - if (matches) { - let dump = extra.clone(); - dump.leafName = matches[1] + '.dmp'; - dump.remove(false); - - let memory = extra.clone(); - memory.leafName = matches[1] + '.memory.json.gz'; - if (memory.exists()) { - memory.remove(false); - } - - extra.remove(false); - } - } - } -} - -function addFormEntry(doc, form, name, value) { - var input = doc.createElement("input"); - input.type = "hidden"; - input.name = name; - input.value = value; - form.appendChild(input); -} - -function writeSubmittedReport(crashID, viewURL) { - var data = strings.crashid.replace("%s", crashID); - if (viewURL) - data += "\n" + strings.reporturl.replace("%s", viewURL); - - writeFile("submitted", crashID + ".txt", data); -} - -// the Submitter class represents an individual submission. -function Submitter(id, recordSubmission, noThrottle, extraExtraKeyVals) { - this.id = id; - this.recordSubmission = recordSubmission; - this.noThrottle = noThrottle; - this.additionalDumps = []; - this.extraKeyVals = extraExtraKeyVals || {}; - this.deferredSubmit = PromiseUtils.defer(); -} - -Submitter.prototype = { - submitSuccess: function Submitter_submitSuccess(ret) - { - // Write out the details file to submitted/ - writeSubmittedReport(ret.CrashID, ret.ViewURL); - - // Delete from pending dir - try { - this.dump.remove(false); - this.extra.remove(false); - - if (this.memory) { - this.memory.remove(false); - } - - for (let i of this.additionalDumps) { - i.dump.remove(false); - } - } - catch (ex) { - // report an error? not much the user can do here. - } - - this.notifyStatus(SUCCESS, ret); - this.cleanup(); - }, - - cleanup: function Submitter_cleanup() { - // drop some references just to be nice - this.iframe = null; - this.dump = null; - this.extra = null; - this.memory = null; - this.additionalDumps = null; - // remove this object from the list of active submissions - let idx = CrashSubmit._activeSubmissions.indexOf(this); - if (idx != -1) - CrashSubmit._activeSubmissions.splice(idx, 1); - }, - - submitForm: function Submitter_submitForm() - { - if (!('ServerURL' in this.extraKeyVals)) { - return false; - } - let serverURL = this.extraKeyVals.ServerURL; - - // Override the submission URL from the environment - - var envOverride = Cc['@mozilla.org/process/environment;1']. - getService(Ci.nsIEnvironment).get("MOZ_CRASHREPORTER_URL"); - if (envOverride != '') { - serverURL = envOverride; - } - - let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(Ci.nsIXMLHttpRequest); - xhr.open("POST", serverURL, true); - - let formData = Cc["@mozilla.org/files/formdata;1"] - .createInstance(Ci.nsIDOMFormData); - // add the data - for (let [name, value] of Object.entries(this.extraKeyVals)) { - if (name != "ServerURL") { - formData.append(name, value); - } - } - if (this.noThrottle) { - // tell the server not to throttle this, since it was manually submitted - formData.append("Throttleable", "0"); - } - // add the minidumps - formData.append("upload_file_minidump", File.createFromFileName(this.dump.path)); - if (this.memory) { - formData.append("memory_report", File.createFromFileName(this.memory.path)); - } - if (this.additionalDumps.length > 0) { - let names = []; - for (let i of this.additionalDumps) { - names.push(i.name); - formData.append("upload_file_minidump_"+i.name, - File.createFromFileName(i.dump.path)); - } - } - - let manager = Services.crashmanager; - let submissionID = manager.generateSubmissionID(); - - xhr.addEventListener("readystatechange", (evt) => { - if (xhr.readyState == 4) { - let ret = - xhr.status == 200 ? parseKeyValuePairs(xhr.responseText) : {}; - let submitted = !!ret.CrashID; - - if (this.recordSubmission) { - let result = submitted ? manager.SUBMISSION_RESULT_OK : - manager.SUBMISSION_RESULT_FAILED; - manager.addSubmissionResult(this.id, submissionID, new Date(), - result); - if (submitted) { - manager.setRemoteCrashID(this.id, ret.CrashID); - } - } - - if (submitted) { - this.submitSuccess(ret); - } - else { - this.notifyStatus(FAILED); - this.cleanup(); - } - } - }, false); - - if (this.recordSubmission) { - manager.addSubmissionAttempt(this.id, submissionID, new Date()); - } - xhr.send(formData); - return true; - }, - - notifyStatus: function Submitter_notify(status, ret) - { - let propBag = Cc["@mozilla.org/hash-property-bag;1"]. - createInstance(Ci.nsIWritablePropertyBag2); - propBag.setPropertyAsAString("minidumpID", this.id); - if (status == SUCCESS) { - propBag.setPropertyAsAString("serverCrashID", ret.CrashID); - } - - let extraKeyValsBag = Cc["@mozilla.org/hash-property-bag;1"]. - createInstance(Ci.nsIWritablePropertyBag2); - for (let key in this.extraKeyVals) { - extraKeyValsBag.setPropertyAsAString(key, this.extraKeyVals[key]); - } - propBag.setPropertyAsInterface("extra", extraKeyValsBag); - - Services.obs.notifyObservers(propBag, "crash-report-status", status); - - switch (status) { - case SUCCESS: - this.deferredSubmit.resolve(ret.CrashID); - break; - case FAILED: - this.deferredSubmit.reject(); - break; - default: - // no callbacks invoked. - } - }, - - submit: function Submitter_submit() - { - let [dump, extra, memory] = getPendingMinidump(this.id); - - if (!dump.exists() || !extra.exists()) { - this.notifyStatus(FAILED); - this.cleanup(); - return this.deferredSubmit.promise; - } - this.dump = dump; - this.extra = extra; - - // The memory file may or may not exist - if (memory.exists()) { - this.memory = memory; - } - - let extraKeyVals = parseKeyValuePairsFromFile(extra); - for (let key in extraKeyVals) { - if (!(key in this.extraKeyVals)) { - this.extraKeyVals[key] = extraKeyVals[key]; - } - } - - let additionalDumps = []; - if ("additional_minidumps" in this.extraKeyVals) { - let names = this.extraKeyVals.additional_minidumps.split(','); - for (let name of names) { - let [dump, extra, memory] = getPendingMinidump(this.id + "-" + name); - if (!dump.exists()) { - this.notifyStatus(FAILED); - this.cleanup(); - return this.deferredSubmit.promise; - } - additionalDumps.push({'name': name, 'dump': dump}); - } - } - - this.notifyStatus(SUBMITTING); - - this.additionalDumps = additionalDumps; - - if (!this.submitForm()) { - this.notifyStatus(FAILED); - this.cleanup(); - } - return this.deferredSubmit.promise; - } -}; - -// =================================== -// External API goes here -this.CrashSubmit = { - /** - * Submit the crash report named id.dmp from the "pending" directory. - * - * @param id - * Filename (minus .dmp extension) of the minidump to submit. - * @param params - * An object containing any of the following optional parameters: - * - recordSubmission - * If true, a submission event is recorded in CrashManager. - * - noThrottle - * If true, this crash report should be submitted with - * an extra parameter of "Throttleable=0" indicating that - * it should be processed right away. This should be set - * when the report is being submitted and the user expects - * to see the results immediately. Defaults to false. - * - extraExtraKeyVals - * An object whose key-value pairs will be merged with the data from - * the ".extra" file submitted with the report. The properties of - * this object will override properties of the same name in the - * .extra file. - * - * @return a Promise that is fulfilled with the server crash ID when the - * submission succeeds and rejected otherwise. - */ - submit: function CrashSubmit_submit(id, params) - { - params = params || {}; - let recordSubmission = false; - let submitSuccess = null; - let submitError = null; - let noThrottle = false; - let extraExtraKeyVals = null; - - if ('recordSubmission' in params) - recordSubmission = params.recordSubmission; - if ('noThrottle' in params) - noThrottle = params.noThrottle; - if ('extraExtraKeyVals' in params) - extraExtraKeyVals = params.extraExtraKeyVals; - - let submitter = new Submitter(id, recordSubmission, - noThrottle, extraExtraKeyVals); - CrashSubmit._activeSubmissions.push(submitter); - return submitter.submit(); - }, - - /** - * Delete the minidup from the "pending" directory. - * - * @param id - * Filename (minus .dmp extension) of the minidump to delete. - */ - delete: function CrashSubmit_delete(id) { - let [dump, extra, memory] = getPendingMinidump(id); - dump.remove(false); - extra.remove(false); - if (memory.exists()) { - memory.remove(false); - } - }, - - /** - * Add a .dmg.ignore file along side the .dmp file to indicate that the user - * shouldn't be prompted to submit this crash report again. - * - * @param id - * Filename (minus .dmp extension) of the report to ignore - */ - - ignore: function CrashSubmit_ignore(id) { - let [dump, extra, mem] = getPendingMinidump(id); - return OS.File.open(dump.path + ".ignore", {create: true}, - {unixFlags: OS.Constants.libc.O_CREAT}) - .then((file) => { file.close(); }); - }, - - /** - * Get the list of pending crash IDs. - * - * @return an array of string, each being an ID as - * expected to be passed to submit() - */ - pendingIDs: function CrashSubmit_pendingIDs() { - return getAllPendingMinidumpsIDs(); - }, - - /** - * Get the list of pending crash IDs, excluding those marked to be ignored - * @param maxFileDate - * A Date object. Any files last modified before that date will be ignored - * - * @return a Promise that is fulfilled with an array of string, each - * being an ID as expected to be passed to submit() or ignore() - */ - pendingIDsAsync: Task.async(function* CrashSubmit_pendingIDsAsync(maxFileDate) { - let ids = []; - let info = null; - try { - info = yield OS.File.stat(getDir("pending").path) - } catch (ex) { - /* pending dir doesn't exist, ignore */ - return ids; - } - - if (info.isDir) { - let iterator = new OS.File.DirectoryIterator(getDir("pending").path); - try { - yield iterator.forEach( - function onEntry(file) { - if (file.name.endsWith(".dmp")) { - return OS.File.exists(file.path + ".ignore") - .then(ignoreExists => { - if (!ignoreExists) { - let id = file.name.slice(0, -4); - if (UUID_REGEX.test(id)) { - return OS.File.stat(file.path) - .then(info => { - if (info.lastAccessDate.valueOf() > - maxFileDate.valueOf()) { - ids.push(id); - } - }); - } - } - return null; - }); - } - return null; - } - ); - } catch (ex) { - Cu.reportError(ex); - } finally { - iterator.close(); - } - } - return ids; - }), - - /** - * Prune the saved dumps. - */ - pruneSavedDumps: function CrashSubmit_pruneSavedDumps() { - pruneSavedDumps(); - }, - - // List of currently active submit objects - _activeSubmissions: [] -}; |