url: require 7+ digits to flag a match as a phone number
AOSP's `Patterns.PHONE` matches any run of 3+ digits, so short codes, ZIP codes, version strings, sport scores, room numbers — anything numeric — surfaced as tappable `tel:` links on Android. iOS `NSDataDetector` is locale-aware and quieter but can still emit short matches in promotional text. Filter both shims at the conventional 7-digit minimum: NANP local is 7 digits, all international numbers run 7+. Below that the match is almost certainly not a dialable number. On Android `canonical_phone` returns an empty string and `run_pattern` drops the record; on iOS the detector block bails early before emitting the match. Fixes user reports of `88773` and `75309` (a famous 7-digit run minus its area code) being incorrectly flagged.
This commit is contained in:
@@ -186,12 +186,22 @@ std::string canonical_web_url(const std::string& match) {
|
|||||||
return std::string("http://") + match;
|
return std::string("http://") + match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AOSP's `Patterns.PHONE` matches any run of 3+ digits, which fires on
|
||||||
|
// short codes, ZIP codes, version strings, sport scores, etc. Require at
|
||||||
|
// least this many digits to call something a dialable number.
|
||||||
|
constexpr int kMinPhoneDigits = 7;
|
||||||
|
|
||||||
std::string canonical_phone(const std::string& match) {
|
std::string canonical_phone(const std::string& match) {
|
||||||
std::string out = "tel:";
|
std::string digits;
|
||||||
for (char c : match) {
|
for (char c : match) {
|
||||||
if ((c >= '0' && c <= '9') || c == '+') out.push_back(c);
|
if ((c >= '0' && c <= '9') || c == '+') digits.push_back(c);
|
||||||
}
|
}
|
||||||
return out;
|
int count = 0;
|
||||||
|
for (char c : digits) {
|
||||||
|
if (c >= '0' && c <= '9') ++count;
|
||||||
|
}
|
||||||
|
if (count < kMinPhoneDigits) return {};
|
||||||
|
return std::string("tel:") + digits;
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_pattern(JNIEnv* env, jstring text, jobject pattern, uint32_t kind,
|
void run_pattern(JNIEnv* env, jstring text, jobject pattern, uint32_t kind,
|
||||||
@@ -229,6 +239,7 @@ void run_pattern(JNIEnv* env, jstring text, jobject pattern, uint32_t kind,
|
|||||||
} else if (kind == kKindPhone) {
|
} else if (kind == kKindPhone) {
|
||||||
url = canonical_phone(group);
|
url = canonical_phone(group);
|
||||||
}
|
}
|
||||||
|
if (url.empty()) continue;
|
||||||
out.push_back(RawMatch{(int32_t)start, (int32_t)end, kind, std::move(url)});
|
out.push_back(RawMatch{(int32_t)start, (int32_t)end, kind, std::move(url)});
|
||||||
}
|
}
|
||||||
env->DeleteLocalRef(matcher);
|
env->DeleteLocalRef(matcher);
|
||||||
|
|||||||
@@ -102,15 +102,21 @@ uint8_t *ux_match_url(const uint16_t *utf16, int32_t len, int32_t *out_size) {
|
|||||||
NSString *folded = [raw decomposedStringWithCompatibilityMapping];
|
NSString *folded = [raw decomposedStringWithCompatibilityMapping];
|
||||||
NSMutableString *digits = [NSMutableString stringWithCapacity:folded.length];
|
NSMutableString *digits = [NSMutableString stringWithCapacity:folded.length];
|
||||||
BOOL seenLetter = NO;
|
BOOL seenLetter = NO;
|
||||||
|
NSUInteger digitCount = 0;
|
||||||
for (NSUInteger i = 0; i < folded.length; i++) {
|
for (NSUInteger i = 0; i < folded.length; i++) {
|
||||||
unichar c = [folded characterAtIndex:i];
|
unichar c = [folded characterAtIndex:i];
|
||||||
if ((c >= '0' && c <= '9') || c == '+') {
|
if ((c >= '0' && c <= '9') || c == '+') {
|
||||||
if (seenLetter) break;
|
if (seenLetter) break;
|
||||||
[digits appendFormat:@"%C", c];
|
[digits appendFormat:@"%C", c];
|
||||||
|
if (c >= '0' && c <= '9') digitCount++;
|
||||||
} else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
|
} else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
|
||||||
seenLetter = YES;
|
seenLetter = YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 7 digits is the conventional minimum dialable
|
||||||
|
// length; below that the match is almost certainly a
|
||||||
|
// short code / version / ZIP / score, not a phone.
|
||||||
|
if (digitCount < 7) return;
|
||||||
url = [NSString stringWithFormat:@"tel:%@", digits];
|
url = [NSString stringWithFormat:@"tel:%@", digits];
|
||||||
kind = kKindPhone;
|
kind = kKindPhone;
|
||||||
} else if (result.resultType == NSTextCheckingTypeLink) {
|
} else if (result.resultType == NSTextCheckingTypeLink) {
|
||||||
|
|||||||
@@ -102,15 +102,21 @@ uint8_t *ux_match_url(const uint16_t *utf16, int32_t len, int32_t *out_size) {
|
|||||||
NSString *folded = [raw decomposedStringWithCompatibilityMapping];
|
NSString *folded = [raw decomposedStringWithCompatibilityMapping];
|
||||||
NSMutableString *digits = [NSMutableString stringWithCapacity:folded.length];
|
NSMutableString *digits = [NSMutableString stringWithCapacity:folded.length];
|
||||||
BOOL seenLetter = NO;
|
BOOL seenLetter = NO;
|
||||||
|
NSUInteger digitCount = 0;
|
||||||
for (NSUInteger i = 0; i < folded.length; i++) {
|
for (NSUInteger i = 0; i < folded.length; i++) {
|
||||||
unichar c = [folded characterAtIndex:i];
|
unichar c = [folded characterAtIndex:i];
|
||||||
if ((c >= '0' && c <= '9') || c == '+') {
|
if ((c >= '0' && c <= '9') || c == '+') {
|
||||||
if (seenLetter) break;
|
if (seenLetter) break;
|
||||||
[digits appendFormat:@"%C", c];
|
[digits appendFormat:@"%C", c];
|
||||||
|
if (c >= '0' && c <= '9') digitCount++;
|
||||||
} else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
|
} else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
|
||||||
seenLetter = YES;
|
seenLetter = YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 7 digits is the conventional minimum dialable
|
||||||
|
// length; below that the match is almost certainly a
|
||||||
|
// short code / version / ZIP / score, not a phone.
|
||||||
|
if (digitCount < 7) return;
|
||||||
url = [NSString stringWithFormat:@"tel:%@", digits];
|
url = [NSString stringWithFormat:@"tel:%@", digits];
|
||||||
kind = kKindPhone;
|
kind = kKindPhone;
|
||||||
} else if (result.resultType == NSTextCheckingTypeLink) {
|
} else if (result.resultType == NSTextCheckingTypeLink) {
|
||||||
|
|||||||
Reference in New Issue
Block a user