summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/accountcreation/Sanitizer.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/accountcreation/Sanitizer.jsm')
-rw-r--r--comm/mail/components/accountcreation/Sanitizer.jsm249
1 files changed, 249 insertions, 0 deletions
diff --git a/comm/mail/components/accountcreation/Sanitizer.jsm b/comm/mail/components/accountcreation/Sanitizer.jsm
new file mode 100644
index 0000000000..d6bc3918bc
--- /dev/null
+++ b/comm/mail/components/accountcreation/Sanitizer.jsm
@@ -0,0 +1,249 @@
+/* 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 EXPORTED_SYMBOLS = ["Sanitizer"];
+
+const { AccountCreationUtils } = ChromeUtils.import(
+ "resource:///modules/accountcreation/AccountCreationUtils.jsm"
+);
+
+const { cleanUpHostName, isLegalHostNameOrIP } = ChromeUtils.import(
+ "resource:///modules/hostnameUtils.jsm"
+);
+
+/**
+ * This is a generic input validation lib. Use it when you process
+ * data from the network.
+ *
+ * Just a few functions which verify, for security purposes, that the
+ * input variables (strings, if nothing else is noted) are of the expected
+ * type and syntax.
+ *
+ * The functions take a string (unless noted otherwise) and return
+ * the expected datatype in JS types. If the value is not as expected,
+ * they throw exceptions.
+ */
+
+// To debug, set mail.setup.loglevel="All" and kDebug = true.
+var kDebug = false;
+
+var Sanitizer = {
+ integer(unchecked) {
+ if (typeof unchecked == "number" && !isNaN(unchecked)) {
+ return unchecked;
+ }
+
+ var r = parseInt(unchecked);
+ if (isNaN(r)) {
+ throw new MalformedException("no_number.error", unchecked);
+ }
+
+ return r;
+ },
+
+ integerRange(unchecked, min, max) {
+ var int = this.integer(unchecked);
+ if (int < min) {
+ throw new MalformedException("number_too_small.error", unchecked);
+ }
+
+ if (int > max) {
+ throw new MalformedException("number_too_large.error", unchecked);
+ }
+
+ return int;
+ },
+
+ boolean(unchecked) {
+ if (typeof unchecked == "boolean") {
+ return unchecked;
+ }
+
+ if (unchecked == "true") {
+ return true;
+ }
+
+ if (unchecked == "false") {
+ return false;
+ }
+
+ throw new MalformedException("boolean.error", unchecked);
+ },
+
+ string(unchecked) {
+ return String(unchecked);
+ },
+
+ nonemptystring(unchecked) {
+ if (!unchecked) {
+ throw new MalformedException("string_empty.error", unchecked);
+ }
+
+ return this.string(unchecked);
+ },
+
+ /**
+ * Allow only letters, numbers, "-" and "_".
+ *
+ * Empty strings not allowed (good idea?).
+ */
+ alphanumdash(unchecked) {
+ var str = this.nonemptystring(unchecked);
+ if (!/^[a-zA-Z0-9\-\_]*$/.test(str)) {
+ throw new MalformedException("alphanumdash.error", unchecked);
+ }
+
+ return str;
+ },
+
+ /**
+ * DNS hostnames like foo.bar.example.com
+ * Allow only letters, numbers, "-" and "."
+ * Empty strings not allowed.
+ * Currently does not support IDN (international domain names).
+ */
+ hostname(unchecked) {
+ let str = cleanUpHostName(this.nonemptystring(unchecked));
+
+ // Allow placeholders. TODO move to a new hostnameOrPlaceholder()
+ // The regex is "anything, followed by one or more (placeholders than
+ // anything)". This doesn't catch the non-placeholder case, but that's
+ // handled down below.
+ if (/^[a-zA-Z0-9\-\.]*(%[A-Z0-9]+%[a-zA-Z0-9\-\.]*)+$/.test(str)) {
+ return str;
+ }
+
+ if (!isLegalHostNameOrIP(str)) {
+ throw new MalformedException("hostname_syntax.error", unchecked);
+ }
+
+ return str.toLowerCase();
+ },
+
+ /**
+ * A value which resembles an email address.
+ */
+ emailAddress(unchecked) {
+ let str = this.nonemptystring(unchecked);
+ if (!/^[a-z0-9\-%+_\.\*]+@[a-z0-9\-\.]+\.[a-z]+$/i.test(str)) {
+ throw new MalformedException("emailaddress_syntax.error", unchecked);
+ }
+
+ return str.toLowerCase();
+ },
+
+ /**
+ * A non-chrome URL that's safe to request.
+ */
+ url(unchecked) {
+ var str = this.string(unchecked);
+
+ // DANGER ZONE: data:text/javascript or data:text/html can contain
+ // JavaScript code, run in the caller's security context, and might allow
+ // arbitrary code execution, so these must be prevented at all costs.
+ // PNG and JPEG data: URLs are fine. But SVG is again dangerous,
+ // it can contain javascript, so it would create a critical security hole.
+ // Talk to BenB or bz before relaxing *any* of the checks in this function.
+ if (
+ str.startsWith("data:image/png;") ||
+ str.startsWith("data:image/jpeg;")
+ ) {
+ return new URL(str).href;
+ }
+
+ if (!str.startsWith("http:") && !str.startsWith("https:")) {
+ throw new MalformedException("url_scheme.error", unchecked);
+ }
+
+ var uri;
+ try {
+ uri = Services.io.newURI(str);
+ uri = uri.QueryInterface(Ci.nsIURL);
+ } catch (e) {
+ throw new MalformedException("url_parsing.error", unchecked);
+ }
+
+ if (uri.scheme != "http" && uri.scheme != "https") {
+ throw new MalformedException("url_scheme.error", unchecked);
+ }
+
+ return uri.spec;
+ },
+
+ /**
+ * A value which should be shown to the user in the UI as label
+ */
+ label(unchecked) {
+ return this.string(unchecked);
+ },
+
+ /**
+ * Allows only certain values as input, otherwise throw.
+ *
+ * @param unchecked {Any} The value to check
+ * @param allowedValues {Array} List of values that |unchecked| may have.
+ * @param defaultValue {Any} (Optional) If |unchecked| does not match
+ * anything in |mapping|, a |defaultValue| can be returned instead of
+ * throwing an exception. The latter is the default and happens when
+ * no |defaultValue| is passed.
+ * @throws MalformedException
+ */
+ enum(unchecked, allowedValues, defaultValue) {
+ for (let allowedValue of allowedValues) {
+ if (allowedValue == unchecked) {
+ return allowedValue;
+ }
+ }
+ // value is bad
+ if (typeof defaultValue == "undefined") {
+ throw new MalformedException("allowed_value.error", unchecked);
+ }
+ return defaultValue;
+ },
+
+ /**
+ * Like enum, allows only certain (string) values as input, but allows the
+ * caller to specify another value to return instead of the input value. E.g.,
+ * if unchecked == "foo", return 1, if unchecked == "bar", return 2,
+ * otherwise throw. This allows to translate string enums into integer enums.
+ *
+ * @param unchecked {Any} The value to check
+ * @param mapping {Object} Associative array. property name is the input
+ * value, property value is the output value. E.g. the example above
+ * would be: { foo: 1, bar : 2 }.
+ * Use quotes when you need freaky characters: "baz-" : 3.
+ * @param defaultValue {Any} (Optional) If |unchecked| does not match
+ * anything in |mapping|, a |defaultValue| can be returned instead of
+ * throwing an exception. The latter is the default and happens when
+ * no |defaultValue| is passed.
+ * @throws MalformedException
+ */
+ translate(unchecked, mapping, defaultValue) {
+ for (var inputValue in mapping) {
+ if (inputValue == unchecked) {
+ return mapping[inputValue];
+ }
+ }
+ // value is bad
+ if (typeof defaultValue == "undefined") {
+ throw new MalformedException("allowed_value.error", unchecked);
+ }
+ return defaultValue;
+ },
+};
+
+function MalformedException(msgID, uncheckedBadValue) {
+ var stringBundle = AccountCreationUtils.getStringBundle(
+ "chrome://messenger/locale/accountCreationUtil.properties"
+ );
+ var msg = stringBundle.GetStringFromName(msgID);
+ if (typeof kDebug != "undefined" && kDebug) {
+ msg += " (bad value: " + uncheckedBadValue + ")";
+ }
+ AccountCreationUtils.Exception.call(this, msg);
+}
+MalformedException.prototype = Object.create(
+ AccountCreationUtils.Exception.prototype
+);
+MalformedException.prototype.constructor = MalformedException;