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 */
isISODigit(char c)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, *, # , + */
isNonSeparator(char c)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 */
matchIntlPrefix(const char * a,int len)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') */
matchTrunkPrefix(const char * a,int len)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 */
matchIntlPrefixAndCC(const char * a,int len)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 */
minPositive(int a,int b)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 */
indexOf(const char * a,char b)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 */
phone_number_compare_loose(const char * a,const char * b)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