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 /** or -1 if both are negative */ 158 static int minPositive(int a, int b) 159 { 160 if (a >= 0 && b >= 0) { 161 return (a < b) ? a : b; 162 } else if (a >= 0) { /* && b < 0 */ 163 return a; 164 } else if (b >= 0) { /* && a < 0 */ 165 return b; 166 } else { /* a < 0 && b < 0 */ 167 return -1; 168 } 169 } 170 171 /** 172 * Return the offset into a of the first appearance of b, or -1 if there 173 * is no such character in a. 174 */ 175 static int indexOf(const char *a, char b) { 176 const char *ix = strchr(a, b); 177 178 if (ix == NULL) 179 return -1; 180 else 181 return ix - a; 182 } 183 184 /** 185 * Compare phone numbers a and b, return true if they're identical 186 * enough for caller ID purposes. 187 * 188 * - Compares from right to left 189 * - requires MIN_MATCH (7) characters to match 190 * - handles common trunk prefixes and international prefixes 191 * (basically, everything except the Russian trunk prefix) 192 * 193 * Tolerates nulls 194 */ 195 bool phone_number_compare_loose(const char* a, const char* b) 196 { 197 int ia, ib; 198 int matched; 199 int numSeparatorCharsInA = 0; 200 int numSeparatorCharsInB = 0; 201 202 if (a == NULL || b == NULL) { 203 return false; 204 } 205 206 ia = strlen(a); 207 ib = strlen(b); 208 if (ia == 0 || ib == 0) { 209 return false; 210 } 211 212 // Compare from right to left 213 ia--; 214 ib--; 215 216 matched = 0; 217 218 while (ia >= 0 && ib >=0) { 219 char ca, cb; 220 bool skipCmp = false; 221 222 ca = a[ia]; 223 224 if (!isNonSeparator(ca)) { 225 ia--; 226 skipCmp = true; 227 numSeparatorCharsInA++; 228 } 229 230 cb = b[ib]; 231 232 if (!isNonSeparator(cb)) { 233 ib--; 234 skipCmp = true; 235 numSeparatorCharsInB++; 236 } 237 238 if (!skipCmp) { 239 if (cb != ca) { 240 break; 241 } 242 ia--; ib--; matched++; 243 } 244 } 245 246 if (matched < MIN_MATCH) { 247 const int effectiveALen = strlen(a) - numSeparatorCharsInA; 248 const int effectiveBLen = strlen(b) - numSeparatorCharsInB; 249 250 // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH, 251 // treat them as equal (i.e. 404-04 and 40404) 252 if (effectiveALen == effectiveBLen && effectiveALen == matched) { 253 return true; 254 } 255 256 return false; 257 } 258 259 // At least one string has matched completely; 260 if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { 261 return true; 262 } 263 264 /* 265 * Now, what remains must be one of the following for a 266 * match: 267 * 268 * - a '+' on one and a '00' or a '011' on the other 269 * - a '0' on one and a (+,00)<country code> on the other 270 * (for this, a '0' and a '00' prefix would have succeeded above) 271 */ 272 273 if (matchIntlPrefix(a, ia + 1) && matchIntlPrefix(b, ib +1)) { 274 return true; 275 } 276 277 if (matchTrunkPrefix(a, ia + 1) && matchIntlPrefixAndCC(b, ib +1)) { 278 return true; 279 } 280 281 if (matchTrunkPrefix(b, ib + 1) && matchIntlPrefixAndCC(a, ia +1)) { 282 return true; 283 } 284 285 /* 286 * Last resort: if the number of unmatched characters on both sides is less than or equal 287 * to the length of the longest country code and only one number starts with a + accept 288 * the match. This is because some countries like France and Russia have an extra prefix 289 * digit that is used when dialing locally in country that does not show up when you dial 290 * the number using the country code. In France this prefix digit is used to determine 291 * which land line carrier to route the call over. 292 */ 293 bool aPlusFirst = (*a == '+'); 294 bool bPlusFirst = (*b == '+'); 295 if (ia < 4 && ib < 4 && (aPlusFirst || bPlusFirst) && !(aPlusFirst && bPlusFirst)) { 296 return true; 297 } 298 299 return false; 300 } 301 302 } // namespace android 303