forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			695 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			695 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // Sourced from https://github.com/apple/password-manager-resources/blob/5f6da89483e75cdc4165a6fc4756796e0ced7a21/tools/PasswordRulesParser.js
 | |
| // Copyright (c) 2019 - 2020 Apple Inc. Licensed under MIT License.
 | |
| 
 | |
| export const PasswordRulesParser = {
 | |
|   parsePasswordRules,
 | |
| };
 | |
| 
 | |
| const Identifier = {
 | |
|   ASCII_PRINTABLE: "ascii-printable",
 | |
|   DIGIT: "digit",
 | |
|   LOWER: "lower",
 | |
|   SPECIAL: "special",
 | |
|   UNICODE: "unicode",
 | |
|   UPPER: "upper",
 | |
| };
 | |
| 
 | |
| const RuleName = {
 | |
|   ALLOWED: "allowed",
 | |
|   MAX_CONSECUTIVE: "max-consecutive",
 | |
|   REQUIRED: "required",
 | |
|   MIN_LENGTH: "minlength",
 | |
|   MAX_LENGTH: "maxlength",
 | |
| };
 | |
| 
 | |
| const CHARACTER_CLASS_START_SENTINEL = "[";
 | |
| const CHARACTER_CLASS_END_SENTINEL = "]";
 | |
| const PROPERTY_VALUE_SEPARATOR = ",";
 | |
| const PROPERTY_SEPARATOR = ";";
 | |
| const PROPERTY_VALUE_START_SENTINEL = ":";
 | |
| 
 | |
| const SPACE_CODE_POINT = " ".codePointAt(0);
 | |
| 
 | |
| const SHOULD_NOT_BE_REACHED = "Should not be reached";
 | |
| 
 | |
| class Rule {
 | |
|   constructor(name, value) {
 | |
|     this._name = name;
 | |
|     this.value = value;
 | |
|   }
 | |
|   get name() {
 | |
|     return this._name;
 | |
|   }
 | |
|   toString() {
 | |
|     return JSON.stringify(this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| class NamedCharacterClass {
 | |
|   constructor(name) {
 | |
|     console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name));
 | |
|     this._name = name;
 | |
|   }
 | |
|   get name() {
 | |
|     return this._name.toLowerCase();
 | |
|   }
 | |
|   toString() {
 | |
|     return this._name;
 | |
|   }
 | |
|   toHTMLString() {
 | |
|     return this._name;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class CustomCharacterClass {
 | |
|   constructor(characters) {
 | |
|     console.assert(characters instanceof Array);
 | |
|     this._characters = characters;
 | |
|   }
 | |
|   get characters() {
 | |
|     return this._characters;
 | |
|   }
 | |
|   toString() {
 | |
|     return `[${this._characters.join("")}]`;
 | |
|   }
 | |
|   toHTMLString() {
 | |
|     return `[${this._characters.join("").replace('"', """)}]`;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // MARK: Lexer functions
 | |
| 
 | |
| function _isIdentifierCharacter(c) {
 | |
|   console.assert(c.length === 1);
 | |
|   return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "-";
 | |
| }
 | |
| 
 | |
| function _isASCIIDigit(c) {
 | |
|   console.assert(c.length === 1);
 | |
|   return c >= "0" && c <= "9";
 | |
| }
 | |
| 
 | |
| function _isASCIIPrintableCharacter(c) {
 | |
|   console.assert(c.length === 1);
 | |
|   return c >= " " && c <= "~";
 | |
| }
 | |
| 
 | |
| function _isASCIIWhitespace(c) {
 | |
|   console.assert(c.length === 1);
 | |
|   return c === " " || c === "\f" || c === "\n" || c === "\r" || c === "\t";
 | |
| }
 | |
| 
 | |
| // MARK: ASCII printable character bit set and canonicalization functions
 | |
| 
 | |
| function _bitSetIndexForCharacter(c) {
 | |
|   console.assert(c.length == 1);
 | |
|   return c.codePointAt(0) - SPACE_CODE_POINT;
 | |
| }
 | |
| 
 | |
| function _characterAtBitSetIndex(index) {
 | |
|   return String.fromCodePoint(index + SPACE_CODE_POINT);
 | |
| }
 | |
| 
 | |
| function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) {
 | |
|   console.assert(bitSet instanceof Array);
 | |
|   console.assert(namedCharacterClass.name !== Identifier.UNICODE);
 | |
|   console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE);
 | |
|   if (namedCharacterClass.name === Identifier.UPPER) {
 | |
|     bitSet.fill(
 | |
|       true,
 | |
|       _bitSetIndexForCharacter("A"),
 | |
|       _bitSetIndexForCharacter("Z") + 1
 | |
|     );
 | |
|   } else if (namedCharacterClass.name === Identifier.LOWER) {
 | |
|     bitSet.fill(
 | |
|       true,
 | |
|       _bitSetIndexForCharacter("a"),
 | |
|       _bitSetIndexForCharacter("z") + 1
 | |
|     );
 | |
|   } else if (namedCharacterClass.name === Identifier.DIGIT) {
 | |
|     bitSet.fill(
 | |
|       true,
 | |
|       _bitSetIndexForCharacter("0"),
 | |
|       _bitSetIndexForCharacter("9") + 1
 | |
|     );
 | |
|   } else if (namedCharacterClass.name === Identifier.SPECIAL) {
 | |
|     bitSet.fill(
 | |
|       true,
 | |
|       _bitSetIndexForCharacter(" "),
 | |
|       _bitSetIndexForCharacter("/") + 1
 | |
|     );
 | |
|     bitSet.fill(
 | |
|       true,
 | |
|       _bitSetIndexForCharacter(":"),
 | |
|       _bitSetIndexForCharacter("@") + 1
 | |
|     );
 | |
|     bitSet.fill(
 | |
|       true,
 | |
|       _bitSetIndexForCharacter("["),
 | |
|       _bitSetIndexForCharacter("`") + 1
 | |
|     );
 | |
|     bitSet.fill(
 | |
|       true,
 | |
|       _bitSetIndexForCharacter("{"),
 | |
|       _bitSetIndexForCharacter("~") + 1
 | |
|     );
 | |
|   } else {
 | |
|     console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) {
 | |
|   for (let character of customCharacterClass.characters) {
 | |
|     bitSet[_bitSetIndexForCharacter(character)] = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function _canonicalizedPropertyValues(
 | |
|   propertyValues,
 | |
|   keepCustomCharacterClassFormatCompliant
 | |
| ) {
 | |
|   let asciiPrintableBitSet = new Array(
 | |
|     "~".codePointAt(0) - " ".codePointAt(0) + 1
 | |
|   );
 | |
| 
 | |
|   for (let propertyValue of propertyValues) {
 | |
|     if (propertyValue instanceof NamedCharacterClass) {
 | |
|       if (propertyValue.name === Identifier.UNICODE) {
 | |
|         return [new NamedCharacterClass(Identifier.UNICODE)];
 | |
|       }
 | |
| 
 | |
|       if (propertyValue.name === Identifier.ASCII_PRINTABLE) {
 | |
|         return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
 | |
|       }
 | |
| 
 | |
|       _markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue);
 | |
|     } else if (propertyValue instanceof CustomCharacterClass) {
 | |
|       _markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   let charactersSeen = [];
 | |
| 
 | |
|   function checkRange(start, end) {
 | |
|     let temp = [];
 | |
|     for (
 | |
|       let i = _bitSetIndexForCharacter(start);
 | |
|       i <= _bitSetIndexForCharacter(end);
 | |
|       ++i
 | |
|     ) {
 | |
|       if (asciiPrintableBitSet[i]) {
 | |
|         temp.push(_characterAtBitSetIndex(i));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let result =
 | |
|       temp.length ===
 | |
|       _bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1;
 | |
|     if (!result) {
 | |
|       charactersSeen = charactersSeen.concat(temp);
 | |
|     }
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   let hasAllUpper = checkRange("A", "Z");
 | |
|   let hasAllLower = checkRange("a", "z");
 | |
|   let hasAllDigits = checkRange("0", "9");
 | |
| 
 | |
|   // Check for special characters, accounting for characters that are given special treatment (i.e. '-' and ']')
 | |
|   let hasAllSpecial = false;
 | |
|   let hasDash = false;
 | |
|   let hasRightSquareBracket = false;
 | |
|   let temp = [];
 | |
|   for (
 | |
|     let i = _bitSetIndexForCharacter(" ");
 | |
|     i <= _bitSetIndexForCharacter("/");
 | |
|     ++i
 | |
|   ) {
 | |
|     if (!asciiPrintableBitSet[i]) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     let character = _characterAtBitSetIndex(i);
 | |
|     if (keepCustomCharacterClassFormatCompliant && character === "-") {
 | |
|       hasDash = true;
 | |
|     } else {
 | |
|       temp.push(character);
 | |
|     }
 | |
|   }
 | |
|   for (
 | |
|     let i = _bitSetIndexForCharacter(":");
 | |
|     i <= _bitSetIndexForCharacter("@");
 | |
|     ++i
 | |
|   ) {
 | |
|     if (asciiPrintableBitSet[i]) {
 | |
|       temp.push(_characterAtBitSetIndex(i));
 | |
|     }
 | |
|   }
 | |
|   for (
 | |
|     let i = _bitSetIndexForCharacter("[");
 | |
|     i <= _bitSetIndexForCharacter("`");
 | |
|     ++i
 | |
|   ) {
 | |
|     if (!asciiPrintableBitSet[i]) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     let character = _characterAtBitSetIndex(i);
 | |
|     if (keepCustomCharacterClassFormatCompliant && character === "]") {
 | |
|       hasRightSquareBracket = true;
 | |
|     } else {
 | |
|       temp.push(character);
 | |
|     }
 | |
|   }
 | |
|   for (
 | |
|     let i = _bitSetIndexForCharacter("{");
 | |
|     i <= _bitSetIndexForCharacter("~");
 | |
|     ++i
 | |
|   ) {
 | |
|     if (asciiPrintableBitSet[i]) {
 | |
|       temp.push(_characterAtBitSetIndex(i));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (hasDash) {
 | |
|     temp.unshift("-");
 | |
|   }
 | |
|   if (hasRightSquareBracket) {
 | |
|     temp.push("]");
 | |
|   }
 | |
| 
 | |
|   let numberOfSpecialCharacters =
 | |
|     _bitSetIndexForCharacter("/") -
 | |
|     _bitSetIndexForCharacter(" ") +
 | |
|     1 +
 | |
|     (_bitSetIndexForCharacter("@") - _bitSetIndexForCharacter(":") + 1) +
 | |
|     (_bitSetIndexForCharacter("`") - _bitSetIndexForCharacter("[") + 1) +
 | |
|     (_bitSetIndexForCharacter("~") - _bitSetIndexForCharacter("{") + 1);
 | |
|   hasAllSpecial = temp.length === numberOfSpecialCharacters;
 | |
|   if (!hasAllSpecial) {
 | |
|     charactersSeen = charactersSeen.concat(temp);
 | |
|   }
 | |
| 
 | |
|   let result = [];
 | |
|   if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) {
 | |
|     return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
 | |
|   }
 | |
|   if (hasAllUpper) {
 | |
|     result.push(new NamedCharacterClass(Identifier.UPPER));
 | |
|   }
 | |
|   if (hasAllLower) {
 | |
|     result.push(new NamedCharacterClass(Identifier.LOWER));
 | |
|   }
 | |
|   if (hasAllDigits) {
 | |
|     result.push(new NamedCharacterClass(Identifier.DIGIT));
 | |
|   }
 | |
|   if (hasAllSpecial) {
 | |
|     result.push(new NamedCharacterClass(Identifier.SPECIAL));
 | |
|   }
 | |
|   if (charactersSeen.length) {
 | |
|     result.push(new CustomCharacterClass(charactersSeen));
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| // MARK: Parser functions
 | |
| 
 | |
| function _indexOfNonWhitespaceCharacter(input, position = 0) {
 | |
|   console.assert(position >= 0);
 | |
|   console.assert(position <= input.length);
 | |
| 
 | |
|   let length = input.length;
 | |
|   while (position < length && _isASCIIWhitespace(input[position])) {
 | |
|     ++position;
 | |
|   }
 | |
| 
 | |
|   return position;
 | |
| }
 | |
| 
 | |
| function _parseIdentifier(input, position) {
 | |
|   console.assert(position >= 0);
 | |
|   console.assert(position < input.length);
 | |
|   console.assert(_isIdentifierCharacter(input[position]));
 | |
| 
 | |
|   let length = input.length;
 | |
|   let seenIdentifiers = [];
 | |
|   do {
 | |
|     let c = input[position];
 | |
|     if (!_isIdentifierCharacter(c)) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     seenIdentifiers.push(c);
 | |
|     ++position;
 | |
|   } while (position < length);
 | |
| 
 | |
|   return [seenIdentifiers.join(""), position];
 | |
| }
 | |
| 
 | |
| function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) {
 | |
|   return (
 | |
|     identifier && Object.values(Identifier).includes(identifier.toLowerCase())
 | |
|   );
 | |
| }
 | |
| 
 | |
| function _parseCustomCharacterClass(input, position) {
 | |
|   console.assert(position >= 0);
 | |
|   console.assert(position < input.length);
 | |
|   console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL);
 | |
| 
 | |
|   let length = input.length;
 | |
|   ++position;
 | |
|   if (position >= length) {
 | |
|     console.error("Found end-of-line instead of character class character");
 | |
|     return [null, position];
 | |
|   }
 | |
| 
 | |
|   let initialPosition = position;
 | |
|   let result = [];
 | |
|   do {
 | |
|     let c = input[position];
 | |
|     if (!_isASCIIPrintableCharacter(c)) {
 | |
|       ++position;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (c === "-" && position - initialPosition > 0) {
 | |
|       // FIXME: Should this be an error?
 | |
|       console.warn(
 | |
|         "Ignoring '-'; a '-' may only appear as the first character in a character class"
 | |
|       );
 | |
|       ++position;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     result.push(c);
 | |
|     ++position;
 | |
|     if (c === CHARACTER_CLASS_END_SENTINEL) {
 | |
|       break;
 | |
|     }
 | |
|   } while (position < length);
 | |
| 
 | |
|   if (
 | |
|     (position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) ||
 | |
|     (position == length && input[position - 1] == CHARACTER_CLASS_END_SENTINEL)
 | |
|   ) {
 | |
|     // Fix up result; we over consumed.
 | |
|     result.pop();
 | |
|     return [result, position];
 | |
|   }
 | |
| 
 | |
|   if (position < length && input[position] == CHARACTER_CLASS_END_SENTINEL) {
 | |
|     return [result, position + 1];
 | |
|   }
 | |
| 
 | |
|   console.error("Found end-of-line instead of end of character class");
 | |
|   return [null, position];
 | |
| }
 | |
| 
 | |
| function _parsePasswordRequiredOrAllowedPropertyValue(input, position) {
 | |
|   console.assert(position >= 0);
 | |
|   console.assert(position < input.length);
 | |
| 
 | |
|   let length = input.length;
 | |
|   let propertyValues = [];
 | |
|   while (true) {
 | |
|     if (_isIdentifierCharacter(input[position])) {
 | |
|       let identifierStartPosition = position;
 | |
|       var [propertyValue, position] = _parseIdentifier(input, position);
 | |
|       if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) {
 | |
|         console.error(
 | |
|           "Unrecognized property value identifier: " + propertyValue
 | |
|         );
 | |
|         return [null, identifierStartPosition];
 | |
|       }
 | |
|       propertyValues.push(new NamedCharacterClass(propertyValue));
 | |
|     } else if (input[position] == CHARACTER_CLASS_START_SENTINEL) {
 | |
|       var [propertyValue, position] = _parseCustomCharacterClass(
 | |
|         input,
 | |
|         position
 | |
|       );
 | |
|       if (propertyValue && propertyValue.length) {
 | |
|         propertyValues.push(new CustomCharacterClass(propertyValue));
 | |
|       }
 | |
|     } else {
 | |
|       console.error(
 | |
|         "Failed to find start of property value: " + input.substr(position)
 | |
|       );
 | |
|       return [null, position];
 | |
|     }
 | |
| 
 | |
|     position = _indexOfNonWhitespaceCharacter(input, position);
 | |
|     if (position >= length || input[position] === PROPERTY_SEPARATOR) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (input[position] === PROPERTY_VALUE_SEPARATOR) {
 | |
|       position = _indexOfNonWhitespaceCharacter(input, position + 1);
 | |
|       if (position >= length) {
 | |
|         console.error(
 | |
|           "Found end-of-line instead of start of next property value"
 | |
|         );
 | |
|         return [null, position];
 | |
|       }
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     console.error(
 | |
|       "Failed to find start of next property or property value: " +
 | |
|         input.substr(position)
 | |
|     );
 | |
|     return [null, position];
 | |
|   }
 | |
|   return [propertyValues, position];
 | |
| }
 | |
| 
 | |
| function _parsePasswordRule(input, position) {
 | |
|   console.assert(position >= 0);
 | |
|   console.assert(position < input.length);
 | |
|   console.assert(_isIdentifierCharacter(input[position]));
 | |
| 
 | |
|   let length = input.length;
 | |
| 
 | |
|   let mayBeIdentifierStartPosition = position;
 | |
|   var [identifier, position] = _parseIdentifier(input, position);
 | |
|   if (!Object.values(RuleName).includes(identifier)) {
 | |
|     console.error("Unrecognized property name: " + identifier);
 | |
|     return [null, mayBeIdentifierStartPosition];
 | |
|   }
 | |
| 
 | |
|   if (position >= length) {
 | |
|     console.error("Found end-of-line instead of start of property value");
 | |
|     return [null, position];
 | |
|   }
 | |
| 
 | |
|   if (input[position] !== PROPERTY_VALUE_START_SENTINEL) {
 | |
|     console.error(
 | |
|       "Failed to find start of property value: " + input.substr(position)
 | |
|     );
 | |
|     return [null, position];
 | |
|   }
 | |
| 
 | |
|   let property = { name: identifier, value: null };
 | |
| 
 | |
|   position = _indexOfNonWhitespaceCharacter(input, position + 1);
 | |
|   // Empty value
 | |
|   if (position >= length || input[position] === PROPERTY_SEPARATOR) {
 | |
|     return [new Rule(property.name, property.value), position];
 | |
|   }
 | |
| 
 | |
|   switch (identifier) {
 | |
|     case RuleName.ALLOWED:
 | |
|     case RuleName.REQUIRED: {
 | |
|       var [
 | |
|         propertyValue,
 | |
|         position,
 | |
|       ] = _parsePasswordRequiredOrAllowedPropertyValue(input, position);
 | |
|       if (propertyValue) {
 | |
|         property.value = propertyValue;
 | |
|       }
 | |
|       return [new Rule(property.name, property.value), position];
 | |
|     }
 | |
|     case RuleName.MAX_CONSECUTIVE: {
 | |
|       var [propertyValue, position] = _parseMaxConsecutivePropertyValue(
 | |
|         input,
 | |
|         position
 | |
|       );
 | |
|       if (propertyValue) {
 | |
|         property.value = propertyValue;
 | |
|       }
 | |
|       return [new Rule(property.name, property.value), position];
 | |
|     }
 | |
|     case RuleName.MIN_LENGTH:
 | |
|     case RuleName.MAX_LENGTH: {
 | |
|       var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(
 | |
|         input,
 | |
|         position
 | |
|       );
 | |
|       if (propertyValue) {
 | |
|         property.value = propertyValue;
 | |
|       }
 | |
|       return [new Rule(property.name, property.value), position];
 | |
|     }
 | |
|   }
 | |
|   console.assert(false, SHOULD_NOT_BE_REACHED);
 | |
| }
 | |
| 
 | |
| function _parseMinLengthMaxLengthPropertyValue(input, position) {
 | |
|   return _parseInteger(input, position);
 | |
| }
 | |
| 
 | |
| function _parseMaxConsecutivePropertyValue(input, position) {
 | |
|   return _parseInteger(input, position);
 | |
| }
 | |
| 
 | |
| function _parseInteger(input, position) {
 | |
|   console.assert(position >= 0);
 | |
|   console.assert(position < input.length);
 | |
| 
 | |
|   if (!_isASCIIDigit(input[position])) {
 | |
|     console.error(
 | |
|       "Failed to parse value of type integer; not a number: " +
 | |
|         input.substr(position)
 | |
|     );
 | |
|     return [null, position];
 | |
|   }
 | |
| 
 | |
|   let length = input.length;
 | |
|   let initialPosition = position;
 | |
|   let result = 0;
 | |
|   do {
 | |
|     result = 10 * result + parseInt(input[position], 10);
 | |
|     ++position;
 | |
|   } while (
 | |
|     position < length &&
 | |
|     input[position] !== PROPERTY_SEPARATOR &&
 | |
|     _isASCIIDigit(input[position])
 | |
|   );
 | |
| 
 | |
|   if (position >= length || input[position] === PROPERTY_SEPARATOR) {
 | |
|     return [result, position];
 | |
|   }
 | |
| 
 | |
|   console.error(
 | |
|     "Failed to parse value of type integer; not a number: " +
 | |
|       input.substr(initialPosition)
 | |
|   );
 | |
|   return [null, position];
 | |
| }
 | |
| 
 | |
| function _parsePasswordRulesInternal(input) {
 | |
|   let parsedProperties = [];
 | |
|   let length = input.length;
 | |
| 
 | |
|   var position = _indexOfNonWhitespaceCharacter(input);
 | |
|   while (position < length) {
 | |
|     if (!_isIdentifierCharacter(input[position])) {
 | |
|       console.warn(
 | |
|         "Failed to find start of property: " + input.substr(position)
 | |
|       );
 | |
|       return parsedProperties;
 | |
|     }
 | |
| 
 | |
|     var [parsedProperty, position] = _parsePasswordRule(input, position);
 | |
|     if (parsedProperty && parsedProperty.value) {
 | |
|       parsedProperties.push(parsedProperty);
 | |
|     }
 | |
| 
 | |
|     position = _indexOfNonWhitespaceCharacter(input, position);
 | |
|     if (position >= length) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (input[position] === PROPERTY_SEPARATOR) {
 | |
|       position = _indexOfNonWhitespaceCharacter(input, position + 1);
 | |
|       if (position >= length) {
 | |
|         return parsedProperties;
 | |
|       }
 | |
| 
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     console.error(
 | |
|       "Failed to find start of next property: " + input.substr(position)
 | |
|     );
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   return parsedProperties;
 | |
| }
 | |
| 
 | |
| function parsePasswordRules(input, formatRulesForMinifiedVersion) {
 | |
|   let passwordRules = _parsePasswordRulesInternal(input) || [];
 | |
| 
 | |
|   // When formatting rules for minified version, we should keep the formatted rules
 | |
|   // as similar to the input as possible. Avoid copying required rules to allowed rules.
 | |
|   let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion;
 | |
| 
 | |
|   let newPasswordRules = [];
 | |
|   let newAllowedValues = [];
 | |
|   let minimumMaximumConsecutiveCharacters = null;
 | |
|   let maximumMinLength = 0;
 | |
|   let minimumMaxLength = null;
 | |
| 
 | |
|   for (let rule of passwordRules) {
 | |
|     switch (rule.name) {
 | |
|       case RuleName.MAX_CONSECUTIVE:
 | |
|         minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters
 | |
|           ? Math.min(rule.value, minimumMaximumConsecutiveCharacters)
 | |
|           : rule.value;
 | |
|         break;
 | |
| 
 | |
|       case RuleName.MIN_LENGTH:
 | |
|         maximumMinLength = Math.max(rule.value, maximumMinLength);
 | |
|         break;
 | |
| 
 | |
|       case RuleName.MAX_LENGTH:
 | |
|         minimumMaxLength = minimumMaxLength
 | |
|           ? Math.min(rule.value, minimumMaxLength)
 | |
|           : rule.value;
 | |
|         break;
 | |
| 
 | |
|       case RuleName.REQUIRED:
 | |
|         rule.value = _canonicalizedPropertyValues(
 | |
|           rule.value,
 | |
|           formatRulesForMinifiedVersion
 | |
|         );
 | |
|         newPasswordRules.push(rule);
 | |
|         if (!suppressCopyingRequiredToAllowed) {
 | |
|           newAllowedValues = newAllowedValues.concat(rule.value);
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case RuleName.ALLOWED:
 | |
|         newAllowedValues = newAllowedValues.concat(rule.value);
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   newAllowedValues = _canonicalizedPropertyValues(
 | |
|     newAllowedValues,
 | |
|     suppressCopyingRequiredToAllowed
 | |
|   );
 | |
|   if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) {
 | |
|     newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
 | |
|   }
 | |
|   if (newAllowedValues.length) {
 | |
|     newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues));
 | |
|   }
 | |
| 
 | |
|   if (minimumMaximumConsecutiveCharacters !== null) {
 | |
|     newPasswordRules.push(
 | |
|       new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   if (maximumMinLength > 0) {
 | |
|     newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength));
 | |
|   }
 | |
| 
 | |
|   if (minimumMaxLength !== null) {
 | |
|     newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength));
 | |
|   }
 | |
| 
 | |
|   return newPasswordRules;
 | |
| }
 | 
