Merge branch 'mkirk/area-code-inference'

pull/1/head
Matthew Chen 6 years ago
commit 7e3ccad22a

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "PhoneNumber.h"
@ -203,8 +203,10 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
static NSString *result = nil;
static NSString *cachedClientPhoneNumber = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// clientPhoneNumber is the local user's phone number and should never change.
// clientPhoneNumber is the local user's phone number and should never change.
static void (^updateCachedClientPhoneNumber)(void);
updateCachedClientPhoneNumber = ^(void) {
NSNumber *localCallingCode = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode];
if (localCallingCode != nil) {
NSString *localCallingCodePrefix = [NSString stringWithFormat:@"+%@", localCallingCode];
@ -214,11 +216,30 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
NBMetadataHelper *helper = [[NBMetadataHelper alloc] init];
NBPhoneMetaData *localNumberRegionMetadata = [helper getMetadataForRegion:localCountryCode];
result = localNumberRegionMetadata.nationalPrefixTransformRule;
} else {
result = nil;
}
}
cachedClientPhoneNumber = [clientPhoneNumber copy];
};
#ifdef DEBUG
// For performance, we want to cahce this result, but it breaks tests since local number
// can change.
if (CurrentAppContext().isRunningTests) {
updateCachedClientPhoneNumber();
} else {
dispatch_once(&onceToken, ^{
updateCachedClientPhoneNumber();
});
}
#else
dispatch_once(&onceToken, ^{
updateCachedClientPhoneNumber();
});
OWSAssertDebug([cachedClientPhoneNumber isEqualToString:clientPhoneNumber]);
#endif
return result;
}
@ -303,38 +324,166 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
// Order matters; better results should appear first so prefer
// matches with the same country code as this client's phone number.
OWSAssertDebug(clientPhoneNumber.length > 0);
if (clientPhoneNumber.length > 0) {
// Note that NBPhoneNumber uses "country code" to refer to what we call a
// "calling code" (i.e. 44 in +44123123). Within SSK we use "country code"
// (and sometimes "region code") to refer to a country's ISO 2-letter code
// (ISO 3166-1 alpha-2).
NSNumber *callingCodeForLocalNumber = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode];
if (callingCodeForLocalNumber != nil) {
NSString *callingCodePrefix = [NSString stringWithFormat:@"+%@", callingCodeForLocalNumber];
tryParsingWithCountryCode(
[callingCodePrefix stringByAppendingString:sanitizedString], [self defaultCountryCode]);
// Try to determine what the country code is for the local phone number
// and also try parsing the phone number using that country code if it
// differs from the device's region code.
//
// For example, a French person living in Italy might have an
// Italian phone number but use French region/language for their
// phone. They're likely to have both Italian and French contacts.
NSString *localCountryCode =
[PhoneNumberUtil.sharedThreadLocal probableCountryCodeForCallingCode:callingCodePrefix];
if (localCountryCode && ![localCountryCode isEqualToString:[self defaultCountryCode]]) {
tryParsingWithCountryCode(
[callingCodePrefix stringByAppendingString:sanitizedString], localCountryCode);
}
}
if (clientPhoneNumber.length == 0) {
OWSFailDebug(@"clientPhoneNumber had unexpected length");
return result;
}
// Note that NBPhoneNumber uses "country code" to refer to what we call a
// "calling code" (i.e. 44 in +44123123). Within SSK we use "country code"
// (and sometimes "region code") to refer to a country's ISO 2-letter code
// (ISO 3166-1 alpha-2).
NSNumber *callingCodeForLocalNumber = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode];
if (callingCodeForLocalNumber == nil) {
OWSFailDebug(@"callingCodeForLocalNumber was unexpectedly nil");
return result;
}
NSString *callingCodePrefix = [NSString stringWithFormat:@"+%@", callingCodeForLocalNumber];
tryParsingWithCountryCode([callingCodePrefix stringByAppendingString:sanitizedString], [self defaultCountryCode]);
// Try to determine what the country code is for the local phone number
// and also try parsing the phone number using that country code if it
// differs from the device's region code.
//
// For example, a French person living in Italy might have an
// Italian phone number but use French region/language for their
// phone. They're likely to have both Italian and French contacts.
NSString *localCountryCode =
[PhoneNumberUtil.sharedThreadLocal probableCountryCodeForCallingCode:callingCodePrefix];
if (localCountryCode && ![localCountryCode isEqualToString:[self defaultCountryCode]]) {
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<NSTextCheckingResult *> *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<NSTextCheckingResult *> *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];

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "PhoneNumber.h"
@ -13,13 +13,10 @@
@implementation PhoneNumberTest
#ifdef BROKEN_TESTS
-(void)testE164 {
XCTAssertEqualObjects(@"+19025555555", [[PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:@"+1 (902) 555-5555"] toE164]);
XCTAssertEqualObjects(@"+19025555555", [[PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:@"1 (902) 555-5555"] toE164]);
XCTAssertEqualObjects(@"+19025555555", [[PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:@"1-902-555-5555"] toE164]);
XCTAssertEqualObjects(@"+19025555555", [[PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:@"1-902--5555"] toE164]);
// Phone numbers missing a calling code.
XCTAssertEqualObjects(@"+19025555555", [[PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:@"9025555555"] toE164]);
@ -132,6 +129,74 @@
XCTAssertTrue([parsed containsObject:@"+13235551234"]);
}
#endif
- (void)testMissingAreaCode_USA
{
// Add area code to numbers that look like "local" numbers
NSArray<NSString *> *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<NSString *> *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

Loading…
Cancel
Save