/* 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/. */ "use strict"; this.EXPORTED_SYMBOLS = ["FormData"]; const Cu = Components.utils; const Ci = Components.interfaces; Cu.import("resource://gre/modules/XPathGenerator.jsm"); /** * Returns whether the given URL very likely has input * fields that contain serialized session store data. */ function isRestorationPage(url) { return url == "about:sessionrestore" || url == "about:welcomeback"; } /** * Returns whether the given form |data| object contains nested restoration * data for a page like about:sessionrestore or about:welcomeback. */ function hasRestorationData(data) { if (isRestorationPage(data.url) && data.id) { return typeof(data.id.sessionData) == "object"; } return false; } /** * Returns the given document's current URI and strips * off the URI's anchor part, if any. */ function getDocumentURI(doc) { return doc.documentURI.replace(/#.*$/, ""); } /** * Returns whether the given value is a valid credit card number based on * the Luhn algorithm. See https://en.wikipedia.org/wiki/Luhn_algorithm. */ function isValidCCNumber(value) { // Remove dashes and whitespace. let ccNumber = value.replace(/[-\s]+/g, ""); // Check for non-alphanumeric characters. if (/[^0-9]/.test(ccNumber)) { return false; } // Check for invalid length. let length = ccNumber.length; if (length != 9 && length != 15 && length != 16) { return false; } let total = 0; for (let i = 0; i < length; i++) { let currentChar = ccNumber.charAt(length - i - 1); let currentDigit = parseInt(currentChar, 10); if (i % 2) { // Double every other value. total += currentDigit * 2; // If the doubled value has two digits, add the digits together. if (currentDigit > 4) { total -= 9; } } else { total += currentDigit; } } return total % 10 == 0; } /** * The public API exported by this module that allows to collect * and restore form data for a document and its subframes. */ this.FormData = Object.freeze({ collect: function (frame) { return FormDataInternal.collect(frame); }, restoreTree: function (root, data) { FormDataInternal.restoreTree(root, data); } }); /** * This module's internal API. */ var FormDataInternal = { /** * Collect form data for a given |frame| *not* including any subframes. * * The returned object may have an "id", "xpath", or "innerHTML" key or a * combination of those three. Form data stored under "id" is for input * fields with id attributes. Data stored under "xpath" is used for input * fields that don't have a unique id and need to be queried using XPath. * The "innerHTML" key is used for editable documents (designMode=on). * * Example: * { * id: {input1: "value1", input3: "value3"}, * xpath: { * "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value2", * "/xhtml:html/xhtml:body/xhtml:input[@name='input4']" : "value4" * } * } * * @param doc * DOMDocument instance to obtain form data for. * @return object * Form data encoded in an object. */ collect: function ({document: doc}) { let formNodes = doc.evaluate( XPathGenerator.restorableFormNodes, doc, XPathGenerator.resolveNS, Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null ); let node; let ret = {}; // Limit the number of XPath expressions for performance reasons. See // bug 477564. const MAX_TRAVERSED_XPATHS = 100; let generatedCount = 0; while ((node = formNodes.iterateNext())) { let hasDefaultValue = true; let value; // Only generate a limited number of XPath expressions for perf reasons // (cf. bug 477564) if (!node.id && generatedCount > MAX_TRAVERSED_XPATHS) { continue; } // We do not want to collect credit card numbers. if (node instanceof Ci.nsIDOMHTMLInputElement && isValidCCNumber(node.value)) { continue; } if (node instanceof Ci.nsIDOMHTMLInputElement || node instanceof Ci.nsIDOMHTMLTextAreaElement || node instanceof Ci.nsIDOMXULTextBoxElement) { switch (node.type) { case "checkbox": case "radio": value = node.checked; hasDefaultValue = value == node.defaultChecked; break; case "file": value = { type: "file", fileList: node.mozGetFileNameArray() }; hasDefaultValue = !value.fileList.length; break; default: // text, textarea value = node.value; hasDefaultValue = value == node.defaultValue; break; } } else if (!node.multiple) { // s with the multiple attribute are easier to determine the // default value since each