Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
4 // Don't modify this code. Please use:
5 // https://github.com/andreasgal/PhoneNumber.js
7 "use strict";
9 this.EXPORTED_SYMBOLS = ["PhoneNumber"];
11 Components.utils.import("resource://gre/modules/PhoneNumberMetaData.jsm");
12 Components.utils.import("resource://gre/modules/PhoneNumberNormalizer.jsm");
14 this.PhoneNumber = (function (dataBase) {
15 // Use strict in our context only - users might not want it
16 'use strict';
18 const MAX_PHONE_NUMBER_LENGTH = 50;
19 const NON_ALPHA_CHARS = /[^a-zA-Z]/g;
20 const NON_DIALABLE_CHARS = /[^,#+\*\d]/g;
21 const NON_DIALABLE_CHARS_ONCE = new RegExp(NON_DIALABLE_CHARS.source);
22 const BACKSLASH = /\\/g;
23 const SPLIT_FIRST_GROUP = /^(\d+)(.*)$/;
24 const LEADING_PLUS_CHARS_PATTERN = /^[+\uFF0B]+/g;
26 // Format of the string encoded meta data. If the name contains "^" or "$"
27 // we will generate a regular expression from the value, with those special
28 // characters as prefix/suffix.
29 const META_DATA_ENCODING = ["region",
30 "^(?:internationalPrefix)",
31 "nationalPrefix",
32 "^(?:nationalPrefixForParsing)",
33 "nationalPrefixTransformRule",
34 "nationalPrefixFormattingRule",
35 "^possiblePattern$",
36 "^nationalPattern$",
37 "formats"];
39 const FORMAT_ENCODING = ["^pattern$",
40 "nationalFormat",
41 "^leadingDigits",
42 "nationalPrefixFormattingRule",
43 "internationalFormat"];
45 var regionCache = Object.create(null);
47 // Parse an array of strings into a convenient object. We store meta
48 // data as arrays since thats much more compact than JSON.
49 function ParseArray(array, encoding, obj) {
50 for (var n = 0; n < encoding.length; ++n) {
51 var value = array[n];
52 if (!value)
53 continue;
54 var field = encoding[n];
55 var fieldAlpha = field.replace(NON_ALPHA_CHARS, "");
56 if (field != fieldAlpha)
57 value = new RegExp(field.replace(fieldAlpha, value));
58 obj[fieldAlpha] = value;
59 }
60 return obj;
61 }
63 // Parse string encoded meta data into a convenient object
64 // representation.
65 function ParseMetaData(countryCode, md) {
66 var array = eval(md.replace(BACKSLASH, "\\\\"));
67 md = ParseArray(array,
68 META_DATA_ENCODING,
69 { countryCode: countryCode });
70 regionCache[md.region] = md;
71 return md;
72 }
74 // Parse string encoded format data into a convenient object
75 // representation.
76 function ParseFormat(md) {
77 var formats = md.formats;
78 if (!formats) {
79 return null;
80 }
81 // Bail if we already parsed the format definitions.
82 if (!(Array.isArray(formats[0])))
83 return;
84 for (var n = 0; n < formats.length; ++n) {
85 formats[n] = ParseArray(formats[n],
86 FORMAT_ENCODING,
87 {});
88 }
89 }
91 // Search for the meta data associated with a region identifier ("US") in
92 // our database, which is indexed by country code ("1"). Since we have
93 // to walk the entire database for this, we cache the result of the lookup
94 // for future reference.
95 function FindMetaDataForRegion(region) {
96 // Check in the region cache first. This will find all entries we have
97 // already resolved (parsed from a string encoding).
98 var md = regionCache[region];
99 if (md)
100 return md;
101 for (var countryCode in dataBase) {
102 var entry = dataBase[countryCode];
103 // Each entry is a string encoded object of the form '["US..', or
104 // an array of strings. We don't want to parse the string here
105 // to save memory, so we just substring the region identifier
106 // and compare it. For arrays, we compare against all region
107 // identifiers with that country code. We skip entries that are
108 // of type object, because they were already resolved (parsed into
109 // an object), and their country code should have been in the cache.
110 if (Array.isArray(entry)) {
111 for (var n = 0; n < entry.length; n++) {
112 if (typeof entry[n] == "string" && entry[n].substr(2,2) == region) {
113 if (n > 0) {
114 // Only the first entry has the formats field set.
115 // Parse the main country if we haven't already and use
116 // the formats field from the main country.
117 if (typeof entry[0] == "string")
118 entry[0] = ParseMetaData(countryCode, entry[0]);
119 let formats = entry[0].formats;
120 let current = ParseMetaData(countryCode, entry[n]);
121 current.formats = formats;
122 return entry[n] = current;
123 }
125 entry[n] = ParseMetaData(countryCode, entry[n]);
126 return entry[n];
127 }
128 }
129 continue;
130 }
131 if (typeof entry == "string" && entry.substr(2,2) == region)
132 return dataBase[countryCode] = ParseMetaData(countryCode, entry);
133 }
134 }
136 // Format a national number for a given region. The boolean flag "intl"
137 // indicates whether we want the national or international format.
138 function FormatNumber(regionMetaData, number, intl) {
139 // We lazily parse the format description in the meta data for the region,
140 // so make sure to parse it now if we haven't already done so.
141 ParseFormat(regionMetaData);
142 var formats = regionMetaData.formats;
143 if (!formats) {
144 return null;
145 }
146 for (var n = 0; n < formats.length; ++n) {
147 var format = formats[n];
148 // The leading digits field is optional. If we don't have it, just
149 // use the matching pattern to qualify numbers.
150 if (format.leadingDigits && !format.leadingDigits.test(number))
151 continue;
152 if (!format.pattern.test(number))
153 continue;
154 if (intl) {
155 // If there is no international format, just fall back to the national
156 // format.
157 var internationalFormat = format.internationalFormat;
158 if (!internationalFormat)
159 internationalFormat = format.nationalFormat;
160 // Some regions have numbers that can't be dialed from outside the
161 // country, indicated by "NA" for the international format of that
162 // number format pattern.
163 if (internationalFormat == "NA")
164 return null;
165 // Prepend "+" and the country code.
166 number = "+" + regionMetaData.countryCode + " " +
167 number.replace(format.pattern, internationalFormat);
168 } else {
169 number = number.replace(format.pattern, format.nationalFormat);
170 // The region has a national prefix formatting rule, and it can be overwritten
171 // by each actual number format rule.
172 var nationalPrefixFormattingRule = regionMetaData.nationalPrefixFormattingRule;
173 if (format.nationalPrefixFormattingRule)
174 nationalPrefixFormattingRule = format.nationalPrefixFormattingRule;
175 if (nationalPrefixFormattingRule) {
176 // The prefix formatting rule contains two magic markers, "$NP" and "$FG".
177 // "$NP" will be replaced by the national prefix, and "$FG" with the
178 // first group of numbers.
179 var match = number.match(SPLIT_FIRST_GROUP);
180 if (match) {
181 var firstGroup = match[1];
182 var rest = match[2];
183 var prefix = nationalPrefixFormattingRule;
184 prefix = prefix.replace("$NP", regionMetaData.nationalPrefix);
185 prefix = prefix.replace("$FG", firstGroup);
186 number = prefix + rest;
187 }
188 }
189 }
190 return (number == "NA") ? null : number;
191 }
192 return null;
193 }
195 function NationalNumber(regionMetaData, number) {
196 this.region = regionMetaData.region;
197 this.regionMetaData = regionMetaData;
198 this.nationalNumber = number;
199 }
201 // NationalNumber represents the result of parsing a phone number. We have
202 // three getters on the prototype that format the number in national and
203 // international format. Once called, the getters put a direct property
204 // onto the object, caching the result.
205 NationalNumber.prototype = {
206 // +1 949-726-2896
207 get internationalFormat() {
208 var value = FormatNumber(this.regionMetaData, this.nationalNumber, true);
209 Object.defineProperty(this, "internationalFormat", { value: value, enumerable: true });
210 return value;
211 },
212 // (949) 726-2896
213 get nationalFormat() {
214 var value = FormatNumber(this.regionMetaData, this.nationalNumber, false);
215 Object.defineProperty(this, "nationalFormat", { value: value, enumerable: true });
216 return value;
217 },
218 // +19497262896
219 get internationalNumber() {
220 var value = this.internationalFormat ? this.internationalFormat.replace(NON_DIALABLE_CHARS, "")
221 : null;
222 Object.defineProperty(this, "internationalNumber", { value: value, enumerable: true });
223 return value;
224 },
225 // country name 'US'
226 get countryName() {
227 var value = this.region ? this.region : null;
228 Object.defineProperty(this, "countryName", { value: value, enumerable: true });
229 return value;
230 }
231 };
233 // Check whether the number is valid for the given region.
234 function IsValidNumber(number, md) {
235 return md.possiblePattern.test(number);
236 }
238 // Check whether the number is a valid national number for the given region.
239 function IsNationalNumber(number, md) {
240 return IsValidNumber(number, md) && md.nationalPattern.test(number);
241 }
243 // Determine the country code a number starts with, or return null if
244 // its not a valid country code.
245 function ParseCountryCode(number) {
246 for (var n = 1; n <= 3; ++n) {
247 var cc = number.substr(0,n);
248 if (dataBase[cc])
249 return cc;
250 }
251 return null;
252 }
254 // Parse an international number that starts with the country code. Return
255 // null if the number is not a valid international number.
256 function ParseInternationalNumber(number) {
257 var ret;
259 // Parse and strip the country code.
260 var countryCode = ParseCountryCode(number);
261 if (!countryCode)
262 return null;
263 number = number.substr(countryCode.length);
265 // Lookup the meta data for the region (or regions) and if the rest of
266 // the number parses for that region, return the parsed number.
267 var entry = dataBase[countryCode];
268 if (Array.isArray(entry)) {
269 for (var n = 0; n < entry.length; ++n) {
270 if (typeof entry[n] == "string")
271 entry[n] = ParseMetaData(countryCode, entry[n]);
272 if (n > 0)
273 entry[n].formats = entry[0].formats;
274 ret = ParseNationalNumber(number, entry[n])
275 if (ret)
276 return ret;
277 }
278 return null;
279 }
280 if (typeof entry == "string")
281 entry = dataBase[countryCode] = ParseMetaData(countryCode, entry);
282 return ParseNationalNumber(number, entry);
283 }
285 // Parse a national number for a specific region. Return null if the
286 // number is not a valid national number (it might still be a possible
287 // number for parts of that region).
288 function ParseNationalNumber(number, md) {
289 if (!md.possiblePattern.test(number) ||
290 !md.nationalPattern.test(number)) {
291 return null;
292 }
293 // Success.
294 return new NationalNumber(md, number);
295 }
297 // Parse a number and transform it into the national format, removing any
298 // international dial prefixes and country codes.
299 function ParseNumber(number, defaultRegion) {
300 var ret;
302 // Remove formating characters and whitespace.
303 number = PhoneNumberNormalizer.Normalize(number);
305 // If there is no defaultRegion, we can't parse international access codes.
306 if (!defaultRegion && number[0] !== '+')
307 return null;
309 // Detect and strip leading '+'.
310 if (number[0] === '+')
311 return ParseInternationalNumber(number.replace(LEADING_PLUS_CHARS_PATTERN, ""));
313 // Lookup the meta data for the given region.
314 var md = FindMetaDataForRegion(defaultRegion.toUpperCase());
316 // See if the number starts with an international prefix, and if the
317 // number resulting from stripping the code is valid, then remove the
318 // prefix and flag the number as international.
319 if (md.internationalPrefix.test(number)) {
320 var possibleNumber = number.replace(md.internationalPrefix, "");
321 ret = ParseInternationalNumber(possibleNumber)
322 if (ret)
323 return ret;
324 }
326 // This is not an international number. See if its a national one for
327 // the current region. National numbers can start with the national
328 // prefix, or without.
329 if (md.nationalPrefixForParsing) {
330 // Some regions have specific national prefix parse rules. Apply those.
331 var withoutPrefix = number.replace(md.nationalPrefixForParsing,
332 md.nationalPrefixTransformRule || '');
333 ret = ParseNationalNumber(withoutPrefix, md)
334 if (ret)
335 return ret;
336 } else {
337 // If there is no specific national prefix rule, just strip off the
338 // national prefix from the beginning of the number (if there is one).
339 var nationalPrefix = md.nationalPrefix;
340 if (nationalPrefix && number.indexOf(nationalPrefix) == 0 &&
341 (ret = ParseNationalNumber(number.substr(nationalPrefix.length), md))) {
342 return ret;
343 }
344 }
345 ret = ParseNationalNumber(number, md)
346 if (ret)
347 return ret;
349 // Now lets see if maybe its an international number after all, but
350 // without '+' or the international prefix.
351 ret = ParseInternationalNumber(number)
352 if (ret)
353 return ret;
355 // If the number matches the possible numbers of the current region,
356 // return it as a possible number.
357 if (md.possiblePattern.test(number))
358 return new NationalNumber(md, number);
360 // We couldn't parse the number at all.
361 return null;
362 }
364 function IsPlainPhoneNumber(number) {
365 if (typeof number !== 'string') {
366 return false;
367 }
369 var length = number.length;
370 var isTooLong = (length > MAX_PHONE_NUMBER_LENGTH);
371 var isEmpty = (length === 0);
372 return !(isTooLong || isEmpty || NON_DIALABLE_CHARS_ONCE.test(number));
373 }
375 return {
376 IsPlain: IsPlainPhoneNumber,
377 Parse: ParseNumber,
378 };
379 })(PHONE_NUMBER_META_DATA);