diff --git a/cpp/src/phonenumbers/phonenumberutil.cc b/cpp/src/phonenumbers/phonenumberutil.cc index 5ce540e473..ec53db1b8a 100644 --- a/cpp/src/phonenumbers/phonenumberutil.cc +++ b/cpp/src/phonenumbers/phonenumberutil.cc @@ -97,11 +97,13 @@ const char kRfc3966ExtnPrefix[] = ";ext="; const char kRfc3966Prefix[] = "tel:"; const char kRfc3966PhoneContext[] = ";phone-context="; const char kRfc3966IsdnSubaddress[] = ";isub="; +const char kRfc3966VisualSeparator[] = "[\\-\\.\\(\\)]?"; const char kDigits[] = "\\p{Nd}"; // We accept alpha characters in phone numbers, ASCII only. We store lower-case // here only since our regular expressions are case-insensitive. const char kValidAlpha[] = "a-z"; +const char kValidAlphaInclUppercase[] = "A-Za-z"; // Default extension prefix to use when formatting. This will be put in front of // any extension component of the number, after the main national number is @@ -654,6 +656,13 @@ class PhoneNumberRegExpsAndMappings { // indicators. When matching, these are hardly ever used to indicate this. const string extn_patterns_for_parsing_; + // Regular expressions of different parts of the phone-context parameter, + // following the syntax defined in RFC3966. + const std::string rfc3966_phone_digit_; + const std::string alphanum_; + const std::string rfc3966_domainlabel_; + const std::string rfc3966_toplabel_; + public: scoped_ptr regexp_factory_; scoped_ptr regexp_cache_; @@ -756,6 +765,14 @@ class PhoneNumberRegExpsAndMappings { scoped_ptr plus_chars_pattern_; + // Regular expression of valid global-number-digits for the phone-context + // parameter, following the syntax defined in RFC3966. + std::unique_ptr rfc3966_global_number_digits_pattern_; + + // Regular expression of valid domainname for the phone-context parameter, + // following the syntax defined in RFC3966. + std::unique_ptr rfc3966_domainname_pattern_; + PhoneNumberRegExpsAndMappings() : valid_phone_number_( StrCat(kDigits, "{", PhoneNumberUtil::kMinLengthForNsn, "}|[", @@ -764,6 +781,13 @@ class PhoneNumberRegExpsAndMappings { kDigits, "){3,}[", PhoneNumberUtil::kValidPunctuation, kStarSign, kValidAlpha, kDigits, "]*")), extn_patterns_for_parsing_(CreateExtnPattern(/* for_parsing= */ true)), + rfc3966_phone_digit_( + StrCat("(", kDigits, "|", kRfc3966VisualSeparator, ")")), + alphanum_(StrCat(kValidAlphaInclUppercase, kDigits)), + rfc3966_domainlabel_( + StrCat("[", alphanum_, "]+((\\-)*[", alphanum_, "])*")), + rfc3966_toplabel_(StrCat("[", kValidAlphaInclUppercase, + "]+((\\-)*[", alphanum_, "])*")), regexp_factory_(new RegExpFactory()), regexp_cache_(new RegExpCache(*regexp_factory_.get(), 128)), diallable_char_mappings_(), @@ -809,7 +833,12 @@ class PhoneNumberRegExpsAndMappings { regexp_factory_->CreateRegExp("(\\$\\d)")), carrier_code_pattern_(regexp_factory_->CreateRegExp("\\$CC")), plus_chars_pattern_(regexp_factory_->CreateRegExp( - StrCat("[", PhoneNumberUtil::kPlusChars, "]+"))) { + StrCat("[", PhoneNumberUtil::kPlusChars, "]+"))), + rfc3966_global_number_digits_pattern_(regexp_factory_->CreateRegExp( + StrCat("^\\", kPlusSign, rfc3966_phone_digit_, "*", kDigits, + rfc3966_phone_digit_, "*$"))), + rfc3966_domainname_pattern_(regexp_factory_->CreateRegExp(StrCat( + "^(", rfc3966_domainlabel_, "\\.)*", rfc3966_toplabel_, "\\.?$"))) { InitializeMapsAndSets(); } @@ -2118,30 +2147,78 @@ bool PhoneNumberUtil::CheckRegionForParsing( return true; } +// Extracts the value of the phone-context parameter of number_to_extract_from +// where the index of ";phone-context=" is parameter index_of_phone_context, +// following the syntax defined in RFC3966. +// Returns the extracted string_view (possibly empty), or a nullopt if no +// phone-context parameter is found. +absl::optional PhoneNumberUtil::ExtractPhoneContext( + const string& number_to_extract_from, + const size_t index_of_phone_context) const { + // If no phone-context parameter is present + if (index_of_phone_context == std::string::npos) { + return absl::nullopt; + } + + size_t phone_context_start = + index_of_phone_context + strlen(kRfc3966PhoneContext); + // If phone-context parameter is empty + if (phone_context_start >= number_to_extract_from.length()) { + return ""; + } + + size_t phone_context_end = + number_to_extract_from.find(';', phone_context_start); + // If phone-context is not the last parameter + if (phone_context_end != std::string::npos) { + return number_to_extract_from.substr( + phone_context_start, phone_context_end - phone_context_start); + } else { + return number_to_extract_from.substr(phone_context_start); + } +} + +// Returns whether the value of phoneContext follows the syntax defined in +// RFC3966. +bool PhoneNumberUtil::IsPhoneContextValid( + const absl::optional phone_context) const { + if (!phone_context.has_value()) { + return true; + } + if (phone_context.value().empty()) { + return false; + } + + // Does phone-context value match pattern of global-number-digits or + // domainname + return reg_exps_->rfc3966_global_number_digits_pattern_->FullMatch( + std::string{phone_context.value()}) || + reg_exps_->rfc3966_domainname_pattern_->FullMatch( + std::string{phone_context.value()}); +} + // Converts number_to_parse to a form that we can parse and write it to // national_number if it is written in RFC3966; otherwise extract a possible // number out of it and write to national_number. -void PhoneNumberUtil::BuildNationalNumberForParsing( +PhoneNumberUtil::ErrorType PhoneNumberUtil::BuildNationalNumberForParsing( const string& number_to_parse, string* national_number) const { size_t index_of_phone_context = number_to_parse.find(kRfc3966PhoneContext); - if (index_of_phone_context != string::npos) { - size_t phone_context_start = - index_of_phone_context + strlen(kRfc3966PhoneContext); + + absl::optional phone_context = + ExtractPhoneContext(number_to_parse, index_of_phone_context); + if (!IsPhoneContextValid(phone_context)) { + VLOG(2) << "The phone-context value is invalid."; + return NOT_A_NUMBER; + } + + if (phone_context.has_value()) { // If the phone context contains a phone number prefix, we need to capture // it, whereas domains will be ignored. - if (phone_context_start < (number_to_parse.length() - 1) && - number_to_parse.at(phone_context_start) == kPlusSign[0]) { + if (phone_context.value().at(0) == kPlusSign[0]) { // Additional parameters might follow the phone context. If so, we will // remove them here because the parameters after phone context are not // important for parsing the phone number. - size_t phone_context_end = number_to_parse.find(';', phone_context_start); - if (phone_context_end != string::npos) { - StrAppend( - national_number, number_to_parse.substr( - phone_context_start, phone_context_end - phone_context_start)); - } else { - StrAppend(national_number, number_to_parse.substr(phone_context_start)); - } + StrAppend(national_number, phone_context.value()); } // Now append everything between the "tel:" prefix and the phone-context. @@ -2175,6 +2252,7 @@ void PhoneNumberUtil::BuildNationalNumberForParsing( // we are concerned about deleting content from a potential number string // when there is no strong evidence that the number is actually written in // RFC3966. + return NO_PARSING_ERROR; } // Note if any new field is added to this method that should always be filled @@ -2189,7 +2267,11 @@ PhoneNumberUtil::ErrorType PhoneNumberUtil::ParseHelper( DCHECK(phone_number); string national_number; - BuildNationalNumberForParsing(number_to_parse, &national_number); + PhoneNumberUtil::ErrorType build_national_number_for_parsing_return = + BuildNationalNumberForParsing(number_to_parse, &national_number); + if (build_national_number_for_parsing_return != NO_PARSING_ERROR) { + return build_national_number_for_parsing_return; + } if (!IsViablePhoneNumber(national_number)) { VLOG(2) << "The string supplied did not seem to be a phone number."; diff --git a/cpp/src/phonenumbers/phonenumberutil.h b/cpp/src/phonenumbers/phonenumberutil.h index 2d2dde5338..1a3ddd9204 100644 --- a/cpp/src/phonenumbers/phonenumberutil.h +++ b/cpp/src/phonenumbers/phonenumberutil.h @@ -958,8 +958,14 @@ class PhoneNumberUtil : public Singleton { bool check_region, PhoneNumber* phone_number) const; - void BuildNationalNumberForParsing(const string& number_to_parse, - string* national_number) const; + absl::optional ExtractPhoneContext( + const string& number_to_extract_from, + size_t index_of_phone_context) const; + + bool IsPhoneContextValid(absl::optional phone_context) const; + + ErrorType BuildNationalNumberForParsing(const string& number_to_parse, + string* national_number) const; bool IsShorterThanPossibleNormalNumber(const PhoneMetadata* country_metadata, const string& number) const; diff --git a/cpp/test/phonenumbers/phonenumberutil_test.cc b/cpp/test/phonenumbers/phonenumberutil_test.cc index 7da22b21ef..9bc28185ed 100644 --- a/cpp/test/phonenumbers/phonenumberutil_test.cc +++ b/cpp/test/phonenumbers/phonenumberutil_test.cc @@ -111,6 +111,13 @@ class PhoneNumberUtilTest : public testing::Test { return phone_util_.ContainsOnlyValidDigits(s); } + void AssertThrowsForInvalidPhoneContext(const string number_to_parse) { + PhoneNumber actual_number; + EXPECT_EQ( + PhoneNumberUtil::NOT_A_NUMBER, + phone_util_.Parse(number_to_parse, RegionCode::ZZ(), &actual_number)); + } + const PhoneNumberUtil& phone_util_; private: @@ -3653,13 +3660,6 @@ TEST_F(PhoneNumberUtilTest, ParseNationalNumber) { "tel:253-0000;isub=12345;phone-context=www.google.com", RegionCode::US(), &test_number)); EXPECT_EQ(us_local_number, test_number); - // This is invalid because no "+" sign is present as part of phone-context. - // The phone context is simply ignored in this case just as if it contains a - // domain. - EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, - phone_util_.Parse("tel:2530000;isub=12345;phone-context=1-650", - RegionCode::US(), &test_number)); - EXPECT_EQ(us_local_number, test_number); EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, phone_util_.Parse("tel:2530000;isub=12345;phone-context=1234.com", RegionCode::US(), &test_number)); @@ -4085,7 +4085,7 @@ TEST_F(PhoneNumberUtilTest, FailedParseOnInvalidNumbers) { EXPECT_EQ(PhoneNumber::default_instance(), test_number); // This is invalid because no "+" sign is present as part of phone-context. // This should not succeed in being parsed. - EXPECT_EQ(PhoneNumberUtil::INVALID_COUNTRY_CODE_ERROR, + EXPECT_EQ(PhoneNumberUtil::NOT_A_NUMBER, phone_util_.Parse("tel:555-1234;phone-context=1-331", RegionCode::ZZ(), &test_number)); EXPECT_EQ(PhoneNumber::default_instance(), test_number); @@ -4703,6 +4703,97 @@ TEST_F(PhoneNumberUtilTest, ParseItalianLeadingZeros) { EXPECT_EQ(zeros_number, test_number); } +TEST_F(PhoneNumberUtilTest, ParseWithPhoneContext) { + PhoneNumber expected_number; + expected_number.set_country_code(64); + expected_number.set_national_number(33316005L); + PhoneNumber actual_number; + + // context = ";phone-context=" descriptor + // descriptor = domainname / global-number-digits + + // Valid global-phone-digits + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=+64", + RegionCode::ZZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=+64;{this isn't " + "part of phone-context anymore!}", + RegionCode::ZZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + expected_number.set_national_number(3033316005L); + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=+64-3", + RegionCode::ZZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + expected_number.set_country_code(55); + expected_number.set_national_number(5033316005L); + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=+(555)", + RegionCode::ZZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + expected_number.set_country_code(1); + expected_number.set_national_number(23033316005L); + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=+-1-2.3()", + RegionCode::ZZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + // Valid domainname + expected_number.set_country_code(64); + expected_number.set_national_number(33316005L); + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=abc.nz", + RegionCode::NZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + EXPECT_EQ( + PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=www.PHONE-numb3r.com", + RegionCode::NZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=a", RegionCode::NZ(), + &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=3phone.J.", + RegionCode::NZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + actual_number.Clear(); + + EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR, + phone_util_.Parse("tel:033316005;phone-context=a--z", + RegionCode::NZ(), &actual_number)); + EXPECT_EQ(expected_number, actual_number); + + // Invalid descriptor + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context="); + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context=+"); + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context=64"); + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context=++64"); + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context=+abc"); + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context=."); + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context=3phone"); + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context=a-.nz"); + AssertThrowsForInvalidPhoneContext("tel:033316005;phone-context=a{b}c"); +} + TEST_F(PhoneNumberUtilTest, CanBeInternationallyDialled) { PhoneNumber test_number; test_number.set_country_code(1); diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/NumberParseException.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/NumberParseException.java index 9b084a8fd2..ef3dfa11b8 100644 --- a/java/libphonenumber/src/com/google/i18n/phonenumbers/NumberParseException.java +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/NumberParseException.java @@ -31,9 +31,10 @@ public enum ErrorType { */ INVALID_COUNTRY_CODE, /** - * This generally indicates the string passed in had less than 3 digits in it. More - * specifically, the number failed to match the regular expression VALID_PHONE_NUMBER in - * PhoneNumberUtil.java. + * This indicates the string passed is not a valid number. Either the string had less than 3 + * digits in it or had an invalid phone-context parameter. More specifically, the number failed + * to match the regular expression VALID_PHONE_NUMBER, RFC3966_GLOBAL_NUMBER_DIGITS, or + * RFC3966_DOMAINNAME in PhoneNumberUtil.java. */ NOT_A_NUMBER, /** diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java index 200bea5a7a..1f49d67473 100644 --- a/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java @@ -306,6 +306,27 @@ public class PhoneNumberUtil { private static final String EXTN_PATTERNS_FOR_PARSING = createExtnPattern(true); static final String EXTN_PATTERNS_FOR_MATCHING = createExtnPattern(false); + // Regular expression of valid global-number-digits for the phone-context parameter, following the + // syntax defined in RFC3966. + private static final String RFC3966_VISUAL_SEPARATOR = "[\\-\\.\\(\\)]?"; + private static final String RFC3966_PHONE_DIGIT = + "(" + DIGITS + "|" + RFC3966_VISUAL_SEPARATOR + ")"; + private static final String RFC3966_GLOBAL_NUMBER_DIGITS = + "^\\" + PLUS_SIGN + RFC3966_PHONE_DIGIT + "*" + DIGITS + RFC3966_PHONE_DIGIT + "*$"; + static final Pattern RFC3966_GLOBAL_NUMBER_DIGITS_PATTERN = + Pattern.compile(RFC3966_GLOBAL_NUMBER_DIGITS); + + // Regular expression of valid domainname for the phone-context parameter, following the syntax + // defined in RFC3966. + private static final String ALPHANUM = VALID_ALPHA + DIGITS; + private static final String RFC3966_DOMAINLABEL = + "[" + ALPHANUM + "]+((\\-)*[" + ALPHANUM + "])*"; + private static final String RFC3966_TOPLABEL = + "[" + VALID_ALPHA + "]+((\\-)*[" + ALPHANUM + "])*"; + private static final String RFC3966_DOMAINNAME = + "^(" + RFC3966_DOMAINLABEL + "\\.)*" + RFC3966_TOPLABEL + "\\.?$"; + static final Pattern RFC3966_DOMAINNAME_PATTERN = Pattern.compile(RFC3966_DOMAINNAME); + /** * Helper method for constructing regular expressions for parsing. Creates an expression that * captures up to maxLength digits. @@ -3338,27 +3359,71 @@ private void parseHelper(CharSequence numberToParse, String defaultRegion, phoneNumber.setNationalNumber(Long.parseLong(normalizedNationalNumber.toString())); } + /** + * Extracts the value of the phone-context parameter of numberToExtractFrom where the index of + * ";phone-context=" is the parameter indexOfPhoneContext, following the syntax defined in + * RFC3966. + * + * @return the extracted string (possibly empty), or null if no phone-context parameter is found. + */ + private String extractPhoneContext(String numberToExtractFrom, int indexOfPhoneContext) { + // If no phone-context parameter is present + if (indexOfPhoneContext == -1) { + return null; + } + + int phoneContextStart = indexOfPhoneContext + RFC3966_PHONE_CONTEXT.length(); + // If phone-context parameter is empty + if (phoneContextStart >= numberToExtractFrom.length()) { + return ""; + } + + int phoneContextEnd = numberToExtractFrom.indexOf(';', phoneContextStart); + // If phone-context is not the last parameter + if (phoneContextEnd != -1) { + return numberToExtractFrom.substring(phoneContextStart, phoneContextEnd); + } else { + return numberToExtractFrom.substring(phoneContextStart); + } + } + + /** + * Returns whether the value of phoneContext follows the syntax defined in RFC3966. + */ + private boolean isPhoneContextValid(String phoneContext) { + if (phoneContext == null) { + return true; + } + if (phoneContext.length() == 0) { + return false; + } + + // Does phone-context value match pattern of global-number-digits or domainname + return RFC3966_GLOBAL_NUMBER_DIGITS_PATTERN.matcher(phoneContext).matches() + || RFC3966_DOMAINNAME_PATTERN.matcher(phoneContext).matches(); + } + /** * Converts numberToParse to a form that we can parse and write it to nationalNumber if it is * written in RFC3966; otherwise extract a possible number out of it and write to nationalNumber. */ - private void buildNationalNumberForParsing(String numberToParse, StringBuilder nationalNumber) { + private void buildNationalNumberForParsing(String numberToParse, StringBuilder nationalNumber) + throws NumberParseException { int indexOfPhoneContext = numberToParse.indexOf(RFC3966_PHONE_CONTEXT); - if (indexOfPhoneContext >= 0) { - int phoneContextStart = indexOfPhoneContext + RFC3966_PHONE_CONTEXT.length(); + + String phoneContext = extractPhoneContext(numberToParse, indexOfPhoneContext); + if (!isPhoneContextValid(phoneContext)) { + throw new NumberParseException(NumberParseException.ErrorType.NOT_A_NUMBER, + "The phone-context value is invalid."); + } + if (phoneContext != null) { // If the phone context contains a phone number prefix, we need to capture it, whereas domains // will be ignored. - if (phoneContextStart < (numberToParse.length() - 1) - && numberToParse.charAt(phoneContextStart) == PLUS_SIGN) { + if (phoneContext.charAt(0) == PLUS_SIGN) { // Additional parameters might follow the phone context. If so, we will remove them here - // because the parameters after phone context are not important for parsing the - // phone number. - int phoneContextEnd = numberToParse.indexOf(';', phoneContextStart); - if (phoneContextEnd > 0) { - nationalNumber.append(numberToParse.substring(phoneContextStart, phoneContextEnd)); - } else { - nationalNumber.append(numberToParse.substring(phoneContextStart)); - } + // because the parameters after phone context are not important for parsing the phone + // number. + nationalNumber.append(phoneContext); } // Now append everything between the "tel:" prefix and the phone-context. This should include @@ -3366,8 +3431,8 @@ private void buildNationalNumberForParsing(String numberToParse, StringBuilder n // handle the case when "tel:" is missing, as we have seen in some of the phone number inputs. // In that case, we append everything from the beginning. int indexOfRfc3966Prefix = numberToParse.indexOf(RFC3966_PREFIX); - int indexOfNationalNumber = (indexOfRfc3966Prefix >= 0) - ? indexOfRfc3966Prefix + RFC3966_PREFIX.length() : 0; + int indexOfNationalNumber = + (indexOfRfc3966Prefix >= 0) ? indexOfRfc3966Prefix + RFC3966_PREFIX.length() : 0; nationalNumber.append(numberToParse.substring(indexOfNationalNumber, indexOfPhoneContext)); } else { // Extract a possible number from the string passed in (this strips leading characters that diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java index 6a466e43d2..c97d09c273 100644 --- a/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java @@ -2122,10 +2122,6 @@ public void testParseNationalNumber() throws Exception { phoneUtil.parse("tel:253-0000;phone-context=www.google.com", RegionCode.US)); assertEquals(US_LOCAL_NUMBER, phoneUtil.parse("tel:253-0000;isub=12345;phone-context=www.google.com", RegionCode.US)); - // This is invalid because no "+" sign is present as part of phone-context. The phone context - // is simply ignored in this case just as if it contains a domain. - assertEquals(US_LOCAL_NUMBER, - phoneUtil.parse("tel:2530000;isub=12345;phone-context=1-650", RegionCode.US)); assertEquals(US_LOCAL_NUMBER, phoneUtil.parse("tel:2530000;isub=12345;phone-context=1234.com", RegionCode.US)); @@ -2539,18 +2535,18 @@ public void testFailedParseOnInvalidNumbers() { // succeed in being parsed. String invalidRfcPhoneContext = "tel:555-1234;phone-context=1-331"; phoneUtil.parse(invalidRfcPhoneContext, RegionCode.ZZ); - fail("'Unknown' region code not allowed: should fail."); + fail("phone-context is missing '+' sign: should fail."); } catch (NumberParseException e) { // Expected this exception. assertEquals("Wrong error type stored in exception.", - NumberParseException.ErrorType.INVALID_COUNTRY_CODE, + NumberParseException.ErrorType.NOT_A_NUMBER, e.getErrorType()); } try { // Only the phone-context symbol is present, but no data. String invalidRfcPhoneContext = ";phone-context="; phoneUtil.parse(invalidRfcPhoneContext, RegionCode.ZZ); - fail("No number is present: should fail."); + fail("phone-context can't be empty: should fail."); } catch (NumberParseException e) { // Expected this exception. assertEquals("Wrong error type stored in exception.", @@ -2895,6 +2891,69 @@ public void testParseItalianLeadingZeros() throws Exception { assertEquals(threeZeros, phoneUtil.parse("0000", RegionCode.AU)); } + public void testParseWithPhoneContext() throws Exception { + // context = ";phone-context=" descriptor + // descriptor = domainname / global-number-digits + + // Valid global-phone-digits + assertEquals(NZ_NUMBER, phoneUtil.parse("tel:033316005;phone-context=+64", RegionCode.ZZ)); + assertEquals( + NZ_NUMBER, + phoneUtil.parse( + "tel:033316005;phone-context=+64;{this isn't part of phone-context anymore!}", + RegionCode.ZZ)); + PhoneNumber nzFromPhoneContext = new PhoneNumber(); + nzFromPhoneContext.setCountryCode(64).setNationalNumber(3033316005L); + assertEquals( + nzFromPhoneContext, + phoneUtil.parse("tel:033316005;phone-context=+64-3", RegionCode.ZZ)); + PhoneNumber brFromPhoneContext = new PhoneNumber(); + brFromPhoneContext.setCountryCode(55).setNationalNumber(5033316005L); + assertEquals( + brFromPhoneContext, + phoneUtil.parse("tel:033316005;phone-context=+(555)", RegionCode.ZZ)); + PhoneNumber usFromPhoneContext = new PhoneNumber(); + usFromPhoneContext.setCountryCode(1).setNationalNumber(23033316005L); + assertEquals( + usFromPhoneContext, + phoneUtil.parse("tel:033316005;phone-context=+-1-2.3()", RegionCode.ZZ)); + + // Valid domainname + assertEquals(NZ_NUMBER, phoneUtil.parse("tel:033316005;phone-context=abc.nz", RegionCode.NZ)); + assertEquals( + NZ_NUMBER, + phoneUtil.parse("tel:033316005;phone-context=www.PHONE-numb3r.com", RegionCode.NZ)); + assertEquals(NZ_NUMBER, phoneUtil.parse("tel:033316005;phone-context=a", RegionCode.NZ)); + assertEquals( + NZ_NUMBER, phoneUtil.parse("tel:033316005;phone-context=3phone.J.", RegionCode.NZ)); + assertEquals(NZ_NUMBER, phoneUtil.parse("tel:033316005;phone-context=a--z", RegionCode.NZ)); + + // Invalid descriptor + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context="); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=+"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=64"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=++64"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=+abc"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=."); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=3phone"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=a-.nz"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=a{b}c"); + } + + private void assertThrowsForInvalidPhoneContext(String numberToParse) { + final String numberToParseFinal = numberToParse; + assertEquals( + NumberParseException.ErrorType.NOT_A_NUMBER, + assertThrows( + NumberParseException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + phoneUtil.parse(numberToParseFinal, RegionCode.ZZ); + } + }) + .getErrorType()); + } + public void testCountryWithNoNumberDesc() { // Andorra is a country where we don't have PhoneNumberDesc info in the metadata. PhoneNumber adNumber = new PhoneNumber(); diff --git a/javascript/i18n/phonenumbers/demo-compiled.js b/javascript/i18n/phonenumbers/demo-compiled.js index 6d48d50aa7..a83864d48b 100644 --- a/javascript/i18n/phonenumbers/demo-compiled.js +++ b/javascript/i18n/phonenumbers/demo-compiled.js @@ -3,16 +3,16 @@ Copyright The Closure Library Authors. SPDX-License-Identifier: Apache-2.0 */ -var aa="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};function ba(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;bc&&(c=Math.max(c+e,0));cb?1:ac&&(c=Math.max(c+f,0));cb?1:aa.length?!1:L(Za,a)}function gb(a){return L(Wa,a)?M(a,Pa):M(a,I)}function hb(a){var b=gb(a.toString());B(a);a.a(b)}function ib(a){return null!=a&&(1!=x(a,9)||-1!=v(a,9)[0])} -function M(a,b){for(var c=new A,d,e=a.length,f=0;f=e?c=b:(d=d.substring(0,e),d=M(d,I),0==d.length?c=b:(g=g.clone(),Da(g,4),d=[g],g=w(a,1),b=P(a),g in H?(c=R(c,g,S(g)),e=wb(d,b),null!=e&&(d=e.clone(),e=w(e,4),0b?2:f[f.length-1]=e&&e<=d;++e)if(c=parseInt(a.substring(0,e),10),c in H)return b.a(a.substring(e)),c;return 0} -function Gb(a,b,c,d,e,f){if(0==b.length)return 0;b=new A(b);var g;null!=c&&(g=r(c,11));null==g&&(g="NonMatch");var k=b.toString();if(0==k.length)g=20;else if(J.test(k))k=k.replace(J,""),B(b),b.a(gb(k)),g=1;else{k=new RegExp(g);hb(b);g=b.toString();if(0==g.search(k)){k=g.match(k)[0].length;var m=g.substring(k).match(Sa);m&&null!=m[1]&&0=b.b.length)throw Error("Phone number too short after IDD"); -a=Fb(b,d);if(0!=a)return u(f,1,a),a;throw Error("Invalid country calling code");}if(null!=c&&(g=w(c,10),k=""+g,m=b.toString(),0==m.lastIndexOf(k,0)&&(k=new A(m.substring(k.length)),m=r(c,1),m=new RegExp(w(m,2)),Hb(k,c,null),k=k.toString(),!L(m,b.toString())&&L(m,k)||3==V(a,b.toString(),c,-1))))return d.a(k),e&&u(f,6,10),u(f,1,g),g;u(f,1,0);return 0} -function Hb(a,b,c){var d=a.toString(),e=d.length,f=r(b,15);if(0!=e&&null!=f&&0!=f.length){var g=new RegExp("^(?:"+f+")");if(e=g.exec(d)){f=new RegExp(w(r(b,1),2));var k=L(f,d),m=e.length-1;b=r(b,16);if(null==b||0==b.length||null==e[m]||0==e[m].length){if(!k||L(f,d.substring(e[0].length)))null!=c&&0b.b.length)throw Error("The string supplied is too short to be a phone number");null!=g&&(c=new A,e=new A(b.toString()),Hb(e,g,c),a=V(a,e.toString(),g,-1),2!=a&&4!=a&&5!=a&&(b=e,d&&0a)throw Error("The string supplied is too short to be a phone number");if(17a.length?!1:K(xb,a)}function Eb(a){return K(sb,a)?L(a,kb):L(a,ib)}function Fb(a){var b=Eb(a.toString());A(a);a.g(b)}function Gb(a){return null!=a&&(1!=w(a,9)||-1!=u(a,9)[0])}function L(a,b){for(var c=new z,d,f=a.length,e=0;eb?2:e[e.length-1]=f&&f<=d;++f)if(c=parseInt(a.substring(0,f),10),c in I)return b.g(a.substring(f)),c;return 0} +function tc(a,b,c,d,f,e){if(0==b.length)return 0;b=new z(b);var g;null!=c&&(g=q(c,11));null==g&&(g="NonMatch");var h=b.toString();if(0==h.length)g=20;else if(nb.test(h))h=h.replace(nb,""),A(b),b.g(Eb(h)),g=1;else{h=new RegExp(g);Fb(b);g=b.toString();if(0==g.search(h)){h=g.match(h)[0].length;var m=g.substring(h).match(ob);m&&null!=m[1]&&0=b.h.length)throw Error("Phone number too short after IDD"); +a=sc(b,d);if(0!=a)return t(e,1,a),a;throw Error("Invalid country calling code");}if(null!=c&&(g=v(c,10),h=""+g,m=b.toString(),0==m.lastIndexOf(h,0)&&(h=new z(m.substring(h.length)),m=q(c,1),m=new RegExp(v(m,2)),uc(h,c,null),h=h.toString(),!K(m,b.toString())&&K(m,h)||3==qc(a,b.toString(),c,-1))))return d.g(h),f&&t(e,6,10),t(e,1,g),g;t(e,1,0);return 0} +function uc(a,b,c){var d=a.toString(),f=d.length,e=q(b,15);if(0!=f&&null!=e&&0!=e.length){var g=new RegExp("^(?:"+e+")");if(f=g.exec(d)){e=new RegExp(v(q(b,1),2));var h=K(e,d),m=f.length-1;b=q(b,16);if(null==b||0==b.length||null==f[m]||0==f[m].length){if(!h||K(e,d.substring(f[0].length)))null!=c&&0=b.length)e="";else{var g=b.indexOf(";",e);e=-1!==g?b.substring(e,g):b.substring(e)}var h=e;null==h?g=!0:0===h.length?g=!1:(g=tb.exec(h),h=ub.exec(h),g=null!==g||null!==h);if(!g)throw Error("The string supplied did not seem to be a phone number"); +null!=e?("+"===e.charAt(0)&&f.g(e),e=b.indexOf("tel:"),f.g(b.substring(0<=e?e+4:0,b.indexOf(";phone-context=")))):(e=f.g,g=null!=b?b:"",h=g.search(pb),0<=h?(g=g.substring(h),g=g.replace(rb,""),h=g.search(qb),0<=h&&(g=g.substring(0,h))):g="",e.call(f,g));e=f.toString();g=e.indexOf(";isub=");0b.h.length)throw Error("The string supplied is too short to be a phone number");null!=g&&(c=new z,f=new z(b.toString()),uc(f,g,c),a=qc(a,f.toString(),g,-1),2!=a&&4!=a&&5!=a&&(b=f,d&&0a)throw Error("The string supplied is too short to be a phone number");if(17=fc)B=ma;else if(Z=Z.substring(0,fc),Z=L(Z,ib),0==Z.length)B=ma;else{var gc=Ua.clone();Qa(gc,4);ka=[gc];var Aa=v(e,1),Ba=N(e);if(Aa in I){var hc=O(r,Aa,P(Aa)),Va=Qb(ka,Ba);if(null==Va)var ic=Ba;else{var Wa=Va.clone(), +Ca=v(Va,4);if(0= numberToExtractFrom.length) { + return ""; + } + + /** @type {number} */ + var phoneContextEnd = numberToExtractFrom.indexOf(';', phoneContextStart); + // If phone-context is not the last parameter + if (phoneContextEnd !== -1) { + return numberToExtractFrom.substring(phoneContextStart, + phoneContextEnd); + } else { + return numberToExtractFrom.substring(phoneContextStart); + } + } + + +/** + * Returns whether the value of phoneContext follows the syntax defined in + * RFC3966. + * + * @param {string|null} phoneContext + * @return {boolean} + * @private + */ +i18n.phonenumbers.PhoneNumberUtil.prototype.isPhoneContextValid_ = + function (phoneContext) { + if (phoneContext == null) { + return true; + } + + if (phoneContext.length === 0) { + return false; + } + + var globalNumberDigitsMatcher = + i18n.phonenumbers.PhoneNumberUtil.RFC3966_GLOBAL_NUMBER_DIGITS_PATTERN_.exec( + phoneContext); + var domainnameMatcher = + i18n.phonenumbers.PhoneNumberUtil.RFC3966_DOMAINNAME_PATTERN_.exec( + phoneContext); + // Does phone-context value match pattern of global-number-digits or + // domainname + return globalNumberDigitsMatcher !== null || domainnameMatcher !== null; + } + + /** * Converts numberToParse to a form that we can parse and write it to * nationalNumber if it is written in RFC3966; otherwise extract a possible @@ -4308,71 +4461,66 @@ i18n.phonenumbers.PhoneNumberUtil.prototype.parseHelper_ = * extension. * @param {!goog.string.StringBuffer} nationalNumber a string buffer for storing * the national significant number. + * @throws {Error} * @private */ i18n.phonenumbers.PhoneNumberUtil.prototype.buildNationalNumberForParsing_ = - function(numberToParse, nationalNumber) { + function (numberToParse, nationalNumber) { + var phoneContext = + i18n.phonenumbers.PhoneNumberUtil.prototype.extractPhoneContext_( + numberToParse); + + if (!i18n.phonenumbers.PhoneNumberUtil.prototype.isPhoneContextValid_( + phoneContext)) { + throw new Error(i18n.phonenumbers.Error.NOT_A_NUMBER); + } + if (phoneContext != null) { + // If the phone context contains a phone number prefix, we need to capture + // it, whereas domains will be ignored. + if (phoneContext.charAt(0) === + i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) { + nationalNumber.append(phoneContext); + } - /** @type {number} */ - var indexOfPhoneContext = numberToParse.indexOf( - i18n.phonenumbers.PhoneNumberUtil.RFC3966_PHONE_CONTEXT_); - if (indexOfPhoneContext >= 0) { - var phoneContextStart = indexOfPhoneContext + - i18n.phonenumbers.PhoneNumberUtil.RFC3966_PHONE_CONTEXT_.length; - // If the phone context contains a phone number prefix, we need to capture - // it, whereas domains will be ignored. - // No length check is necessary, as per C++ or Java, since out-of-bounds - // requests to charAt return an empty string. - if (numberToParse.charAt(phoneContextStart) == - i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) { - // Additional parameters might follow the phone context. If so, we will - // remove them here because the parameters after phone context are not - // important for parsing the phone number. - var phoneContextEnd = numberToParse.indexOf(';', phoneContextStart); - if (phoneContextEnd > 0) { - nationalNumber.append(numberToParse.substring(phoneContextStart, - phoneContextEnd)); + // Now append everything between the "tel:" prefix and the phone-context. + // This should include the national number, an optional extension or + // isdn-subaddress component. Note we also handle the case when "tel:" is + // missing, as we have seen in some of the phone number inputs. + // In that case, we append everything from the beginning. + var indexOfRfc3966Prefix = numberToParse.indexOf( + i18n.phonenumbers.PhoneNumberUtil.RFC3966_PREFIX_); + var indexOfNationalNumber = (indexOfRfc3966Prefix >= 0) ? + indexOfRfc3966Prefix + + i18n.phonenumbers.PhoneNumberUtil.RFC3966_PREFIX_.length : 0; + var indexOfPhoneContext = numberToParse.indexOf( + i18n.phonenumbers.PhoneNumberUtil.RFC3966_PHONE_CONTEXT_); + nationalNumber.append(numberToParse.substring(indexOfNationalNumber, + indexOfPhoneContext)); } else { - nationalNumber.append(numberToParse.substring(phoneContextStart)); + // Extract a possible number from the string passed in (this strips leading + // characters that could not be the start of a phone number.) + nationalNumber.append( + i18n.phonenumbers.PhoneNumberUtil.extractPossibleNumber( + numberToParse ?? "")); } - } - - // Now append everything between the "tel:" prefix and the phone-context. - // This should include the national number, an optional extension or - // isdn-subaddress component. Note we also handle the case when "tel:" is - // missing, as we have seen in some of the phone number inputs. - // In that case, we append everything from the beginning. - var indexOfRfc3966Prefix = numberToParse.indexOf( - i18n.phonenumbers.PhoneNumberUtil.RFC3966_PREFIX_); - var indexOfNationalNumber = (indexOfRfc3966Prefix >= 0) ? - indexOfRfc3966Prefix + - i18n.phonenumbers.PhoneNumberUtil.RFC3966_PREFIX_.length : 0; - nationalNumber.append(numberToParse.substring(indexOfNationalNumber, - indexOfPhoneContext)); - } else { - // Extract a possible number from the string passed in (this strips leading - // characters that could not be the start of a phone number.) - nationalNumber.append( - i18n.phonenumbers.PhoneNumberUtil.extractPossibleNumber(numberToParse)); - } - // Delete the isdn-subaddress and everything after it if it is present. - // Note extension won't appear at the same time with isdn-subaddress - // according to paragraph 5.3 of the RFC3966 spec, - /** @type {string} */ - var nationalNumberStr = nationalNumber.toString(); - var indexOfIsdn = nationalNumberStr.indexOf( - i18n.phonenumbers.PhoneNumberUtil.RFC3966_ISDN_SUBADDRESS_); - if (indexOfIsdn > 0) { - nationalNumber.clear(); - nationalNumber.append(nationalNumberStr.substring(0, indexOfIsdn)); - } - // If both phone context and isdn-subaddress are absent but other - // parameters are present, the parameters are left in nationalNumber. This - // is because we are concerned about deleting content from a potential - // number string when there is no strong evidence that the number is - // actually written in RFC3966. -}; + // Delete the isdn-subaddress and everything after it if it is present. + // Note extension won't appear at the same time with isdn-subaddress + // according to paragraph 5.3 of the RFC3966 spec, + /** @type {string} */ + var nationalNumberStr = nationalNumber.toString(); + var indexOfIsdn = nationalNumberStr.indexOf( + i18n.phonenumbers.PhoneNumberUtil.RFC3966_ISDN_SUBADDRESS_); + if (indexOfIsdn > 0) { + nationalNumber.clear(); + nationalNumber.append(nationalNumberStr.substring(0, indexOfIsdn)); + } + // If both phone context and isdn-subaddress are absent but other + // parameters are present, the parameters are left in nationalNumber. This + // is because we are concerned about deleting content from a potential + // number string when there is no strong evidence that the number is + // actually written in RFC3966. + }; /** diff --git a/javascript/i18n/phonenumbers/phonenumberutil_test.js b/javascript/i18n/phonenumbers/phonenumberutil_test.js index ed43b83618..4070dd08a0 100644 --- a/javascript/i18n/phonenumbers/phonenumberutil_test.js +++ b/javascript/i18n/phonenumbers/phonenumberutil_test.js @@ -896,7 +896,7 @@ function testFormatOutOfCountryWithPreferredIntlPrefix() { assertEquals( '0011 39 02 3661 8300', phoneUtil.formatOutOfCountryCallingNumber(IT_NUMBER, RegionCode.AU)); - + // Testing preferred international prefixes with ~ are supported (designates // waiting). assertEquals( @@ -2818,11 +2818,6 @@ function testParseNationalNumber() { 'tel:253-0000;phone-context=www.google.com', RegionCode.US))); assertTrue(US_LOCAL_NUMBER.equals(phoneUtil.parse( 'tel:253-0000;isub=12345;phone-context=www.google.com', RegionCode.US))); - // This is invalid because no "+" sign is present as part of phone-context. - // The phone context is simply ignored in this case just as if it contains a - // domain. - assertTrue(US_LOCAL_NUMBER.equals(phoneUtil.parse( - 'tel:2530000;isub=12345;phone-context=1-650', RegionCode.US))); assertTrue(US_LOCAL_NUMBER.equals(phoneUtil.parse( 'tel:2530000;isub=12345;phone-context=1234.com', RegionCode.US))); @@ -3330,20 +3325,19 @@ function testFailedParseOnInvalidNumbers() { /** @type {string} */ var invalidRfcPhoneContext = 'tel:555-1234;phone-context=1-331'; phoneUtil.parse(invalidRfcPhoneContext, RegionCode.ZZ); - fail('"Unknown" region code not allowed: should fail.'); + fail('phone-context is missing "+" sign: should fail.'); } catch (e) { // Expected this exception. assertEquals( 'Wrong error type stored in exception.', - i18n.phonenumbers.Error.INVALID_COUNTRY_CODE, e.message); + i18n.phonenumbers.Error.NOT_A_NUMBER, e.message); } try { // Only the phone-context symbol is present, but no data. invalidRfcPhoneContext = ';phone-context='; phoneUtil.parse(invalidRfcPhoneContext, RegionCode.ZZ); fail( - 'Should have thrown an exception, no valid country calling code ' + - 'present.'); + 'phone-context can\'t be empty: should fail.'); } catch (e) { // Expected. assertEquals( @@ -3792,6 +3786,68 @@ function testParseItalianLeadingZeros() { assertTrue(threeZeros.equals(phoneUtil.parse('0000', RegionCode.AU))); } +function testParseWithPhoneContext() { + // context = ";phone-context=" descriptor + // descriptor = domainname / global-number-digits + + // Valid global-phone-digits + assertTrue(NZ_NUMBER.equals( + phoneUtil.parse("tel:033316005;phone-context=+64", RegionCode.ZZ))); + assertTrue(NZ_NUMBER.equals(phoneUtil.parse( + "tel:033316005;phone-context=+64;{this isn't part of phone-context anymore!}", + RegionCode.ZZ))); + /** @type {!i18n.phonenumbers.PhoneNumber} */ + var nzFromPhoneContext = new i18n.phonenumbers.PhoneNumber(); + nzFromPhoneContext.setCountryCode(64); + nzFromPhoneContext.setNationalNumber(3033316005); + assertTrue(nzFromPhoneContext.equals( + phoneUtil.parse("tel:033316005;phone-context=+64-3", RegionCode.ZZ))); + /** @type {!i18n.phonenumbers.PhoneNumber} */ + var brFromPhoneContext = new i18n.phonenumbers.PhoneNumber(); + brFromPhoneContext.setCountryCode(55); + brFromPhoneContext.setNationalNumber(5033316005); + assertTrue(brFromPhoneContext.equals( + phoneUtil.parse("tel:033316005;phone-context=+(555)", RegionCode.ZZ))); + /** @type {!i18n.phonenumbers.PhoneNumber} */ + var usFromPhoneContext = new i18n.phonenumbers.PhoneNumber(); + usFromPhoneContext.setCountryCode(1); + usFromPhoneContext.setNationalNumber(23033316005); + assertTrue(usFromPhoneContext.equals( + phoneUtil.parse("tel:033316005;phone-context=+-1-2.3()", RegionCode.ZZ))); + + // Valid domainname + assertTrue(NZ_NUMBER.equals( + phoneUtil.parse("tel:033316005;phone-context=abc.nz", RegionCode.NZ))); + assertTrue(NZ_NUMBER.equals( + phoneUtil.parse("tel:033316005;phone-context=www.PHONE-numb3r.com", + RegionCode.NZ))); + assertTrue(NZ_NUMBER.equals( + phoneUtil.parse("tel:033316005;phone-context=a", RegionCode.NZ))); + assertTrue(NZ_NUMBER.equals( + phoneUtil.parse("tel:033316005;phone-context=3phone.J.", RegionCode.NZ))); + assertTrue(NZ_NUMBER.equals( + phoneUtil.parse("tel:033316005;phone-context=a--z", RegionCode.NZ))); + + // Invalid descriptor + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context="); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=+"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=64"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=++64"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=+abc"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=."); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=3phone"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=a-.nz"); + assertThrowsForInvalidPhoneContext("tel:033316005;phone-context=a{b}c"); +} + +function assertThrowsForInvalidPhoneContext(numberToParse) { + try { + phoneUtil.parse(numberToParse, RegionCode.ZZ); + } catch (e) { + assertEquals(i18n.phonenumbers.Error.NOT_A_NUMBER, e.message); + } +} + function testCountryWithNoNumberDesc() { var PNF = i18n.phonenumbers.PhoneNumberFormat; var PNT = i18n.phonenumbers.PhoneNumberType; diff --git a/pending_code_changes.txt b/pending_code_changes.txt index 8b13789179..3ebe58c237 100644 --- a/pending_code_changes.txt +++ b/pending_code_changes.txt @@ -1 +1,4 @@ - +Code changes: + - Added a check to PhoneNumberUtil that the value of the `phone-context` + parameter of the tel URI follows the correct syntax as defined in + [RFC3966](https://www.rfc-editor.org/rfc/rfc3966#section-3). \ No newline at end of file