From 60f816c7478b4fe509da3f1c6aa17d1868abac56 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 9 Jan 2019 17:17:02 -0700 Subject: [PATCH] Area code inference for US and Brazil --- SignalServiceKit/src/Contacts/PhoneNumber.m | 125 ++++++++++++++++++ .../tests/Contacts/PhoneNumberTest.m | 70 ++++++++++ 2 files changed, 195 insertions(+) diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.m b/SignalServiceKit/src/Contacts/PhoneNumber.m index a0a561600..064c0f81c 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumber.m +++ b/SignalServiceKit/src/Contacts/PhoneNumber.m @@ -356,9 +356,134 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN tryParsingWithCountryCode([callingCodePrefix stringByAppendingString:sanitizedString], localCountryCode); } + NSString *_Nullable phoneNumberByApplyingMissingAreaCode = + [self applyMissingAreaCodeWithCallingCodeForReferenceNumber:callingCodeForLocalNumber + referenceNumber:clientPhoneNumber + sanitizedInputText:sanitizedString]; + if (phoneNumberByApplyingMissingAreaCode) { + tryParsingWithCountryCode(phoneNumberByApplyingMissingAreaCode, localCountryCode); + } + return result; } +#pragma mark - missing area code + ++ (nullable NSString *)applyMissingAreaCodeWithCallingCodeForReferenceNumber:(NSNumber *)callingCodeForReferenceNumber + referenceNumber:(NSString *)referenceNumber + sanitizedInputText:(NSString *)sanitizedInputText +{ + if ([callingCodeForReferenceNumber isEqual:@(55)]) { + return + [self applyMissingBrazilAreaCodeWithReferenceNumber:referenceNumber sanitizedInputText:sanitizedInputText]; + } else if ([callingCodeForReferenceNumber isEqual:@(1)]) { + return [self applyMissingUnitedStatesAreaCodeWithReferenceNumber:referenceNumber + sanitizedInputText:sanitizedInputText]; + } else { + return nil; + } +} + +#pragma mark - missing brazil area code + ++ (nullable NSString *)applyMissingBrazilAreaCodeWithReferenceNumber:(NSString *)referenceNumber + sanitizedInputText:(NSString *)sanitizedInputText +{ + NSError *error; + NSRegularExpression *missingAreaCodeRegex = + [[NSRegularExpression alloc] initWithPattern:@"^(9?\\d{8})$" options:0 error:&error]; + if (error) { + OWSFailDebug(@"failure: %@", error); + return nil; + } + + if ([missingAreaCodeRegex firstMatchInString:sanitizedInputText + options:0 + range:NSMakeRange(0, sanitizedInputText.length)] + == nil) { + } + + NSString *_Nullable referenceAreaCode = [self brazilAreaCodeFromReferenceNumberE164:referenceNumber]; + if (!referenceAreaCode) { + return nil; + } + return [NSString stringWithFormat:@"+55%@%@", referenceAreaCode, sanitizedInputText]; +} + ++ (nullable NSString *)brazilAreaCodeFromReferenceNumberE164:(NSString *)referenceNumberE164 +{ + NSError *error; + NSRegularExpression *areaCodeRegex = + [[NSRegularExpression alloc] initWithPattern:@"^\\+55(\\d{2})9?\\d{8}" options:0 error:&error]; + if (error) { + OWSFailDebug(@"failure: %@", error); + return nil; + } + + NSArray *matches = + [areaCodeRegex matchesInString:referenceNumberE164 options:0 range:NSMakeRange(0, referenceNumberE164.length)]; + if (matches.count == 0) { + OWSFailDebug(@"failure: unexpectedly unable to extract area code from US number"); + return nil; + } + NSTextCheckingResult *match = matches[0]; + + NSRange firstCaptureRange = [match rangeAtIndex:1]; + return [referenceNumberE164 substringWithRange:firstCaptureRange]; +} + +#pragma mark - missing US area code + ++ (nullable NSString *)applyMissingUnitedStatesAreaCodeWithReferenceNumber:(NSString *)referenceNumber + sanitizedInputText:(NSString *)sanitizedInputText +{ + NSError *error; + NSRegularExpression *missingAreaCodeRegex = + [[NSRegularExpression alloc] initWithPattern:@"^(\\d{7})$" options:0 error:&error]; + if (error) { + OWSFailDebug(@"failure: %@", error); + return nil; + } + + if ([missingAreaCodeRegex firstMatchInString:sanitizedInputText + options:0 + range:NSMakeRange(0, sanitizedInputText.length)] + == nil) { + // area code isn't missing + return nil; + } + + NSString *_Nullable referenceAreaCode = [self unitedStateAreaCodeFromReferenceNumberE164:referenceNumber]; + if (!referenceAreaCode) { + return nil; + } + return [NSString stringWithFormat:@"+1%@%@", referenceAreaCode, sanitizedInputText]; +} + ++ (nullable NSString *)unitedStateAreaCodeFromReferenceNumberE164:(NSString *)referenceNumberE164 +{ + NSError *error; + NSRegularExpression *areaCodeRegex = + [[NSRegularExpression alloc] initWithPattern:@"^\\+1(\\d{3})" options:0 error:&error]; + if (error) { + OWSFailDebug(@"failure: %@", error); + return nil; + } + + NSArray *matches = + [areaCodeRegex matchesInString:referenceNumberE164 options:0 range:NSMakeRange(0, referenceNumberE164.length)]; + if (matches.count == 0) { + OWSFailDebug(@"failure: unexpectedly unable to extract area code from US number"); + return nil; + } + NSTextCheckingResult *match = matches[0]; + + NSRange firstCaptureRange = [match rangeAtIndex:1]; + return [referenceNumberE164 substringWithRange:firstCaptureRange]; +} + +#pragma mark - + + (NSString *)removeFormattingCharacters:(NSString *)inputString { char outputString[inputString.length + 1]; diff --git a/SignalServiceKit/tests/Contacts/PhoneNumberTest.m b/SignalServiceKit/tests/Contacts/PhoneNumberTest.m index 35e0ae29f..81b64fe8d 100644 --- a/SignalServiceKit/tests/Contacts/PhoneNumberTest.m +++ b/SignalServiceKit/tests/Contacts/PhoneNumberTest.m @@ -129,4 +129,74 @@ XCTAssertTrue([parsed containsObject:@"+13235551234"]); } +- (void)testMissingAreaCode_USA +{ + // Add area code to numbers that look like "local" numbers + NSArray *parsed = + [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"555-1234" clientPhoneNumber:@"+13233214321"]; + XCTAssertTrue([parsed containsObject:@"+13235551234"]); + + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"5551234" clientPhoneNumber:@"+13233214321"]; + XCTAssertTrue([parsed containsObject:@"+13235551234"]); + + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"555 1234" clientPhoneNumber:@"+13233214321"]; + XCTAssertTrue([parsed containsObject:@"+13235551234"]); + + // Don't touch numbers that look like e164, even if they're the same length as a "local" us number + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"+5551234" clientPhoneNumber:@"+13213214321"]; + XCTAssertTrue([parsed containsObject:@"+5551234"]); + + // Don't touch numbers that already have an area code + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"570 555 1234" clientPhoneNumber:@"+13233214321"]; + XCTAssertTrue([parsed containsObject:@"+15705551234"]); + + // Don't touch numbers that are already in e164 + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"+33170393800" clientPhoneNumber:@"+13213214321"]; + XCTAssertTrue([parsed containsObject:@"+33170393800"]); +} + +- (void)testMissingAreaCode_Brazil +{ + // Add area code to land-line numbers that look like "local" numbers + NSArray *parsed = + [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"87654321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+552187654321"]); + + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"8765-4321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+552187654321"]); + + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"8765 4321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+552187654321"]); + + // Add area code to mobile numbers that look like "local" numbers + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"987654321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+5521987654321"]); + + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"9 8765-4321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+5521987654321"]); + + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"9 8765 4321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+5521987654321"]); + + // Don't touch numbers that look like e164, even if they're the same length as a "local" us number + parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"+3365-4321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+33654321"]); + + // Don't touch land-line numbers that already have an area code + parsed = + [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"22 8765 4321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+552287654321"]); + + // Don't touch mobile numbers that already have an area code + parsed = + [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"22 9 8765 4321" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+5522987654321"]); + + // Don't touch numbers that are already in e164 + parsed = + [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"+33170393800" clientPhoneNumber:@"+5521912345678"]; + XCTAssertTrue([parsed containsObject:@"+33170393800"]); +} + + @end