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
23namespace android {
24
25static int MIN_MATCH = 7;
26
27/** True if c is ISO-LATIN characters 0-9 */
28static bool isISODigit (char c)
29{
30    return c >= '0' && c <= '9';
31}
32
33/** True if c is ISO-LATIN characters 0-9, *, # , +  */
34static 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 */
50static 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') */
88static 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 */
109static 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 */
158static 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 */
175static 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 */
195bool 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