1 /* 2 * 3 * Copyright 2006, The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 // Old implementation for phone_number_compare(), which has used in cupcake, but once replaced with 19 // the new, more strict version, and reverted again. 20 21 #include <string.h> 22 23 namespace android { 24 25 static int MIN_MATCH = 7; 26 27 /** True if c is ISO-LATIN characters 0-9 */ 28 static bool isISODigit (char c) 29 { 30 return c >= '0' && c <= '9'; 31 } 32 33 /** True if c is ISO-LATIN characters 0-9, *, # , + */ 34 static bool isNonSeparator(char c) 35 { 36 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; 37 } 38 39 /** 40 * Phone numbers are stored in "lookup" form in the database 41 * as reversed strings to allow for caller ID lookup 42 * 43 * This method takes a phone number and makes a valid SQL "LIKE" 44 * string that will match the lookup form 45 * 46 */ 47 /** all of a up to len must be an international prefix or 48 * separators/non-dialing digits 49 */ 50 static bool matchIntlPrefix(const char* a, int len) 51 { 52 /* '([^0-9*#+]\+[^0-9*#+] | [^0-9*#+]0(0|11)[^0-9*#+] )$' */ 53 /* 0 1 2 3 45 */ 54 55 int state = 0; 56 for (int i = 0 ; i < len ; i++) { 57 char c = a[i]; 58 59 switch (state) { 60 case 0: 61 if (c == '+') state = 1; 62 else if (c == '0') state = 2; 63 else if (isNonSeparator(c)) return false; 64 break; 65 66 case 2: 67 if (c == '0') state = 3; 68 else if (c == '1') state = 4; 69 else if (isNonSeparator(c)) return false; 70 break; 71 72 case 4: 73 if (c == '1') state = 5; 74 else if (isNonSeparator(c)) return false; 75 break; 76 77 default: 78 if (isNonSeparator(c)) return false; 79 break; 80 81 } 82 } 83 84 return state == 1 || state == 3 || state == 5; 85 } 86 87 /** all of 'a' up to len must match non-US trunk prefix ('0') */ 88 static bool matchTrunkPrefix(const char* a, int len) 89 { 90 bool found; 91 92 found = false; 93 94 for (int i = 0 ; i < len ; i++) { 95 char c = a[i]; 96 97 if (c == '0' && !found) { 98 found = true; 99 } else if (isNonSeparator(c)) { 100 return false; 101 } 102 } 103 104 return found; 105 } 106 107 /** all of 'a' up to len must be a (+|00|011)country code) 108 * We're fast and loose with the country code. Any \d{1,3} matches */ 109 static bool matchIntlPrefixAndCC(const char* a, int len) 110 { 111 /* [^0-9*#+]*(\+|0(0|11)\d\d?\d? [^0-9*#+] $ */ 112 /* 0 1 2 3 45 6 7 8 */ 113 114 int state = 0; 115 for (int i = 0 ; i < len ; i++ ) { 116 char c = a[i]; 117 118 switch (state) { 119 case 0: 120 if (c == '+') state = 1; 121 else if (c == '0') state = 2; 122 else if (isNonSeparator(c)) return false; 123 break; 124 125 case 2: 126 if (c == '0') state = 3; 127 else if (c == '1') state = 4; 128 else if (isNonSeparator(c)) return false; 129 break; 130 131 case 4: 132 if (c == '1') state = 5; 133 else if (isNonSeparator(c)) return false; 134 break; 135 136 case 1: 137 case 3: 138 case 5: 139 if (isISODigit(c)) state = 6; 140 else if (isNonSeparator(c)) return false; 141 break; 142 143 case 6: 144 case 7: 145 if (isISODigit(c)) state++; 146 else if (isNonSeparator(c)) return false; 147 break; 148 149 default: 150 if (isNonSeparator(c)) return false; 151 } 152 } 153 154 return state == 6 || state == 7 || state == 8; 155 } 156 157 /** 158 * Compare phone numbers a and b, return true if they're identical 159 * enough for caller ID purposes. 160 * 161 * - Compares from right to left 162 * - requires MIN_MATCH (7) characters to match 163 * - handles common trunk prefixes and international prefixes 164 * (basically, everything except the Russian trunk prefix) 165 * 166 * Tolerates nulls 167 */ 168 bool phone_number_compare_loose(const char* a, const char* b) 169 { 170 int ia, ib; 171 int matched; 172 int numSeparatorCharsInA = 0; 173 int numSeparatorCharsInB = 0; 174 175 if (a == NULL || b == NULL) { 176 return false; 177 } 178 179 ia = strlen(a); 180 ib = strlen(b); 181 if (ia == 0 || ib == 0) { 182 return false; 183 } 184 185 // Compare from right to left 186 ia--; 187 ib--; 188 189 matched = 0; 190 191 while (ia >= 0 && ib >=0) { 192 char ca, cb; 193 bool skipCmp = false; 194 195 ca = a[ia]; 196 197 if (!isNonSeparator(ca)) { 198 ia--; 199 skipCmp = true; 200 numSeparatorCharsInA++; 201 } 202 203 cb = b[ib]; 204 205 if (!isNonSeparator(cb)) { 206 ib--; 207 skipCmp = true; 208 numSeparatorCharsInB++; 209 } 210 211 if (!skipCmp) { 212 if (cb != ca) { 213 break; 214 } 215 ia--; ib--; matched++; 216 } 217 } 218 219 if (matched < MIN_MATCH) { 220 const int effectiveALen = strlen(a) - numSeparatorCharsInA; 221 const int effectiveBLen = strlen(b) - numSeparatorCharsInB; 222 223 // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH, 224 // treat them as equal (i.e. 404-04 and 40404) 225 if (effectiveALen == effectiveBLen && effectiveALen == matched) { 226 return true; 227 } 228 229 return false; 230 } 231 232 // At least one string has matched completely; 233 if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { 234 return true; 235 } 236 237 /* 238 * Now, what remains must be one of the following for a 239 * match: 240 * 241 * - a '+' on one and a '00' or a '011' on the other 242 * - a '0' on one and a (+,00)<country code> on the other 243 * (for this, a '0' and a '00' prefix would have succeeded above) 244 */ 245 246 if (matchIntlPrefix(a, ia + 1) && matchIntlPrefix(b, ib + 1)) { 247 return true; 248 } 249 250 if (matchTrunkPrefix(a, ia + 1) && matchIntlPrefixAndCC(b, ib + 1)) { 251 return true; 252 } 253 254 if (matchTrunkPrefix(b, ib + 1) && matchIntlPrefixAndCC(a, ia + 1)) { 255 return true; 256 } 257 258 /* 259 * Last resort: if the number of unmatched characters on both sides is less than or equal 260 * to the length of the longest country code and only one number starts with a + accept 261 * the match. This is because some countries like France and Russia have an extra prefix 262 * digit that is used when dialing locally in country that does not show up when you dial 263 * the number using the country code. In France this prefix digit is used to determine 264 * which land line carrier to route the call over. 265 */ 266 bool aPlusFirst = (*a == '+'); 267 bool bPlusFirst = (*b == '+'); 268 bool aIgnoreUnmatched = aPlusFirst && (ia - ib) >= 0 && (ia - ib) <= 1; 269 bool bIgnoreUnmatched = bPlusFirst && (ib - ia) >= 0 && (ib - ia) <= 1; 270 if (ia < 4 && ib < 4 && (aIgnoreUnmatched || bIgnoreUnmatched) && !(aPlusFirst && bPlusFirst)) { 271 return true; 272 } 273 274 return false; 275 } 276 277 } // namespace android 278