1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2012,2013 Intel Corporation
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Mathias Hasselmann <mathias@openismus.com>
18 */
19
20 #include "evolution-data-server-config.h"
21
22 #ifndef ENABLE_PHONENUMBER
23 #error Phone number support must be enabled for this file
24 #endif /* ENABLE_PHONENUMBER */
25
26 #include "e-phone-number-private.h"
27
28 /* C++ standard library */
29 #include <string>
30
31 /* system headers */
32 #include <langinfo.h>
33 #include <locale.h>
34
35 /* libphonenumber */
36 #include <phonenumbers/logger.h>
37 #include <phonenumbers/phonenumberutil.h>
38
39 using i18n::phonenumbers::PhoneNumber;
40 using i18n::phonenumbers::PhoneNumberUtil;
41
42 struct _EPhoneNumber {
43 PhoneNumber priv;
44 };
45
46 static PhoneNumberUtil *
e_phone_number_util_get_instance(void)47 e_phone_number_util_get_instance (void)
48 {
49 static PhoneNumberUtil *instance = NULL;
50
51 if (g_once_init_enter (&instance)) {
52 /* FIXME: Ideally PhoneNumberUtil would not be a singleton,
53 * so that we could safely tweak its attributes without
54 * influencing other users of the library. */
55 PhoneNumberUtil *new_instance = PhoneNumberUtil::GetInstance ();
56
57 /* Disable all logging: libphonenumber is pretty verbose. */
58 new_instance->SetLogger (new i18n::phonenumbers::NullLogger);
59 g_once_init_leave (&instance, new_instance);
60 }
61
62 return instance;
63 }
64
65 static EPhoneNumberError
e_phone_number_error_code(PhoneNumberUtil::ErrorType error)66 e_phone_number_error_code (PhoneNumberUtil::ErrorType error)
67 {
68 switch (error) {
69 case PhoneNumberUtil::NO_PARSING_ERROR:
70 g_return_val_if_reached (E_PHONE_NUMBER_ERROR_UNKNOWN);
71 case PhoneNumberUtil::INVALID_COUNTRY_CODE_ERROR:
72 return E_PHONE_NUMBER_ERROR_INVALID_COUNTRY_CODE;
73 case PhoneNumberUtil::NOT_A_NUMBER:
74 return E_PHONE_NUMBER_ERROR_NOT_A_NUMBER;
75 case PhoneNumberUtil::TOO_SHORT_AFTER_IDD:
76 return E_PHONE_NUMBER_ERROR_TOO_SHORT_AFTER_IDD;
77 case PhoneNumberUtil::TOO_SHORT_NSN:
78 return E_PHONE_NUMBER_ERROR_TOO_SHORT;
79 case PhoneNumberUtil::TOO_LONG_NSN:
80 return E_PHONE_NUMBER_ERROR_TOO_LONG;
81 }
82
83 /* Please file a bug that we can add a proper error code. */
84 g_return_val_if_reached (E_PHONE_NUMBER_ERROR_UNKNOWN);
85 }
86
87 static std::string
_e_phone_number_cxx_region_code_from_locale(const gchar * locale)88 _e_phone_number_cxx_region_code_from_locale (const gchar *locale)
89 {
90 std::string current_region = locale;
91 const std::string::size_type uscore = current_region.find ('_');
92
93 if (uscore != std::string::npos) {
94 const std::string::size_type n = std::min (uscore + 3, current_region.length ());
95
96 if (n == current_region.length() || not ::isalpha(current_region.at(n)))
97 current_region = current_region.substr (uscore + 1, 2);
98 }
99
100 if (current_region.length() != 2)
101 return "US";
102
103 return current_region;
104 }
105
106 static std::string
_e_phone_number_cxx_make_region_code(const gchar * region_code)107 _e_phone_number_cxx_make_region_code (const gchar *region_code)
108 {
109 if (region_code && strlen (region_code) > 2)
110 return _e_phone_number_cxx_region_code_from_locale (region_code);
111
112 /* Get two-letter country code from current locale's address facet if supported */
113 #if HAVE__NL_ADDRESS_COUNTRY_AB2
114 if (region_code == NULL || region_code[0] == '\0')
115 region_code = nl_langinfo (_NL_ADDRESS_COUNTRY_AB2);
116 #endif /* HAVE__NL_ADDRESS_COUNTRY_AB2 */
117
118 /* Extract two-letter country code from current locale id if needed.
119 * From outside this is a C library, so we better consult the
120 * C infrastructure instead of std::locale, which might divert. */
121 if (region_code == NULL || region_code[0] == '\0') {
122 const gchar *lcl = NULL;
123
124 #if defined (LC_TELEPHONE)
125 lcl = setlocale (LC_TELEPHONE, NULL);
126 #endif
127
128 #if defined (LC_ADDRESS)
129 if (!lcl || !*lcl || (lcl[0] == 'C' && (!lcl[1] || lcl[1] == '.')))
130 lcl = setlocale (LC_ADDRESS, NULL);
131 #endif
132
133 if (!lcl || !*lcl || (lcl[0] == 'C' && (!lcl[1] || lcl[1] == '.')))
134 lcl = setlocale (LC_MESSAGES, NULL);
135
136 return _e_phone_number_cxx_region_code_from_locale (lcl);
137 }
138
139 return region_code;
140 }
141
142 gint
_e_phone_number_cxx_get_country_code_for_region(const gchar * region_code)143 _e_phone_number_cxx_get_country_code_for_region (const gchar *region_code)
144 {
145 return e_phone_number_util_get_instance ()->GetCountryCodeForRegion (
146 _e_phone_number_cxx_make_region_code (region_code));
147 }
148
149 gchar *
_e_phone_number_cxx_get_default_region()150 _e_phone_number_cxx_get_default_region ()
151 {
152 return g_strdup (_e_phone_number_cxx_make_region_code (NULL).c_str ());
153 }
154
155 static bool
_e_phone_number_cxx_parse(const std::string & phone_number,const std::string & region,PhoneNumber * parsed_number,GError ** error)156 _e_phone_number_cxx_parse (const std::string &phone_number,
157 const std::string ®ion,
158 PhoneNumber *parsed_number,
159 GError **error)
160 {
161 const PhoneNumberUtil::ErrorType err =
162 #ifdef PHONENUMBER_RAW_INPUT_NEEDED
163 e_phone_number_util_get_instance ()->ParseAndKeepRawInput (
164 phone_number, region, parsed_number);
165 #else /* PHONENUMBER_RAW_INPUT_NEEDED */
166 e_phone_number_util_get_instance ()->Parse (
167 phone_number, region, parsed_number);
168 #endif /* PHONENUMBER_RAW_INPUT_NEEDED */
169
170 if (err != PhoneNumberUtil::NO_PARSING_ERROR) {
171 _e_phone_number_set_error (error, e_phone_number_error_code (err));
172 return false;
173 }
174
175 return true;
176 }
177
178 EPhoneNumber *
_e_phone_number_cxx_from_string(const gchar * phone_number,const gchar * region_code,GError ** error)179 _e_phone_number_cxx_from_string (const gchar *phone_number,
180 const gchar *region_code,
181 GError **error)
182 {
183 g_return_val_if_fail (NULL != phone_number, NULL);
184
185 const std::string valid_region = _e_phone_number_cxx_make_region_code (region_code);
186 std::auto_ptr<EPhoneNumber> parsed_number(new EPhoneNumber);
187
188 if (!_e_phone_number_cxx_parse (
189 phone_number, valid_region, &parsed_number->priv, error))
190 return NULL;
191
192 return parsed_number.release ();
193 }
194
195 gchar *
_e_phone_number_cxx_to_string(const EPhoneNumber * phone_number,EPhoneNumberFormat format)196 _e_phone_number_cxx_to_string (const EPhoneNumber *phone_number,
197 EPhoneNumberFormat format)
198 {
199 g_return_val_if_fail (NULL != phone_number, NULL);
200
201 std::string formatted_number;
202
203 e_phone_number_util_get_instance ()->Format
204 (phone_number->priv,
205 static_cast<PhoneNumberUtil::PhoneNumberFormat> (format),
206 &formatted_number);
207
208 if (!formatted_number.empty ())
209 return g_strdup (formatted_number.c_str ());
210
211 return NULL;
212 }
213
214 static EPhoneNumberCountrySource
_e_phone_number_cxx_get_country_source(const PhoneNumber & phone_number)215 _e_phone_number_cxx_get_country_source (const PhoneNumber &phone_number)
216 {
217 g_return_val_if_fail (
218 phone_number.has_country_code_source (),
219 E_PHONE_NUMBER_COUNTRY_FROM_DEFAULT);
220
221 switch (phone_number.country_code_source ()) {
222 case PhoneNumber::FROM_NUMBER_WITH_PLUS_SIGN:
223 return E_PHONE_NUMBER_COUNTRY_FROM_FQTN;
224
225 case PhoneNumber::FROM_NUMBER_WITH_IDD:
226 return E_PHONE_NUMBER_COUNTRY_FROM_IDD;
227
228 /* FROM_NUMBER_WITHOUT_PLUS_SIGN only is used internally
229 * by libphonenumber to properly(???) reconstruct raw input
230 * from PhoneNumberUtil::ParseAndKeepRawInput(). Let's not
231 * bother our users with that barely understandable and
232 * almost undocumented implementation detail.
233 */
234 case PhoneNumber::FROM_NUMBER_WITHOUT_PLUS_SIGN:
235 case PhoneNumber::FROM_DEFAULT_COUNTRY:
236 break;
237
238 default:
239 break;
240 }
241
242 return E_PHONE_NUMBER_COUNTRY_FROM_DEFAULT;
243 }
244
245 static EPhoneNumberCountrySource
_e_phone_number_cxx_get_country_source(const EPhoneNumber * phone_number)246 _e_phone_number_cxx_get_country_source (const EPhoneNumber *phone_number)
247 {
248 return _e_phone_number_cxx_get_country_source (phone_number->priv);
249 }
250
251 gint
_e_phone_number_cxx_get_country_code(const EPhoneNumber * phone_number,EPhoneNumberCountrySource * source)252 _e_phone_number_cxx_get_country_code (const EPhoneNumber *phone_number,
253 EPhoneNumberCountrySource *source)
254 {
255 g_return_val_if_fail (NULL != phone_number, 0);
256
257 if (phone_number->priv.has_country_code ()) {
258 if (source)
259 *source = _e_phone_number_cxx_get_country_source (phone_number);
260
261 return phone_number->priv.country_code ();
262 }
263
264 return 0;
265 }
266
267 gchar *
_e_phone_number_cxx_get_national_number(const EPhoneNumber * phone_number)268 _e_phone_number_cxx_get_national_number (const EPhoneNumber *phone_number)
269 {
270 g_return_val_if_fail (NULL != phone_number, NULL);
271
272 std::string national_number;
273
274 e_phone_number_util_get_instance ()->GetNationalSignificantNumber (
275 phone_number->priv, &national_number);
276
277 if (!national_number.empty ())
278 return g_strdup (national_number.c_str ());
279
280 return NULL;
281 }
282
283 static EPhoneNumberMatch
e_phone_number_match(PhoneNumberUtil::MatchType match_type)284 e_phone_number_match (PhoneNumberUtil::MatchType match_type)
285 {
286 switch (match_type) {
287 case PhoneNumberUtil::NO_MATCH:
288 case PhoneNumberUtil::INVALID_NUMBER:
289 return E_PHONE_NUMBER_MATCH_NONE;
290 case PhoneNumberUtil::SHORT_NSN_MATCH:
291 return E_PHONE_NUMBER_MATCH_SHORT;
292 case PhoneNumberUtil::NSN_MATCH:
293 return E_PHONE_NUMBER_MATCH_NATIONAL;
294 case PhoneNumberUtil::EXACT_MATCH:
295 return E_PHONE_NUMBER_MATCH_EXACT;
296 }
297
298 g_return_val_if_reached (E_PHONE_NUMBER_MATCH_NONE);
299 }
300
301 static EPhoneNumberMatch
_e_phone_number_cxx_compare(const PhoneNumber & first_number_in,const PhoneNumber & second_number_in)302 _e_phone_number_cxx_compare (const PhoneNumber &first_number_in,
303 const PhoneNumber &second_number_in)
304 {
305 PhoneNumber first_number(first_number_in);
306 PhoneNumber second_number(second_number_in);
307 const EPhoneNumberCountrySource cs1 =
308 _e_phone_number_cxx_get_country_source (first_number);
309 const EPhoneNumberCountrySource cs2 =
310 _e_phone_number_cxx_get_country_source (second_number);
311
312 /* Must clear guessed country codes, otherwise libphonenumber
313 * includes them in the comparison, leading to false
314 * negatives. */
315 if (cs1 == E_PHONE_NUMBER_COUNTRY_FROM_DEFAULT)
316 first_number.clear_country_code();
317 if (cs2 == E_PHONE_NUMBER_COUNTRY_FROM_DEFAULT)
318 second_number.clear_country_code();
319
320 PhoneNumberUtil::MatchType match_type =
321 e_phone_number_util_get_instance ()->IsNumberMatch (
322 first_number, second_number);
323
324 /* XXX Work around a bug in libphonenumber's C++ implementation
325 *
326 * If we got a short match and either of the numbers is missing
327 * the country code, then make sure both of them have an arbitrary
328 * country code specified... if that matches exactly, then we promote
329 * the short number match to a national match.
330 */
331 if (match_type == PhoneNumberUtil::SHORT_NSN_MATCH &&
332 (first_number.country_code() == 0 ||
333 second_number.country_code() == 0)) {
334
335 second_number.set_country_code (1);
336 first_number.set_country_code (1);
337
338 PhoneNumberUtil::MatchType second_match =
339 e_phone_number_util_get_instance ()->IsNumberMatch (
340 first_number, second_number);
341
342 if (second_match == PhoneNumberUtil::EXACT_MATCH)
343 match_type = PhoneNumberUtil::NSN_MATCH;
344 }
345
346 g_warn_if_fail (match_type != PhoneNumberUtil::INVALID_NUMBER);
347 return e_phone_number_match (match_type);
348 }
349
350 EPhoneNumberMatch
_e_phone_number_cxx_compare(const EPhoneNumber * first_number,const EPhoneNumber * second_number)351 _e_phone_number_cxx_compare (const EPhoneNumber *first_number,
352 const EPhoneNumber *second_number)
353 {
354 g_return_val_if_fail (NULL != first_number, E_PHONE_NUMBER_MATCH_NONE);
355 g_return_val_if_fail (NULL != second_number, E_PHONE_NUMBER_MATCH_NONE);
356
357 return _e_phone_number_cxx_compare (first_number->priv, second_number->priv);
358 }
359
360 EPhoneNumberMatch
_e_phone_number_cxx_compare_strings(const gchar * first_number,const gchar * second_number,const gchar * region_code,GError ** error)361 _e_phone_number_cxx_compare_strings (const gchar *first_number,
362 const gchar *second_number,
363 const gchar *region_code,
364 GError **error)
365 {
366 g_return_val_if_fail (NULL != first_number, E_PHONE_NUMBER_MATCH_NONE);
367 g_return_val_if_fail (NULL != second_number, E_PHONE_NUMBER_MATCH_NONE);
368
369 const std::string region = _e_phone_number_cxx_make_region_code (region_code);
370 PhoneNumber pn1, pn2;
371
372 if (!_e_phone_number_cxx_parse (first_number, region, &pn1, error))
373 return E_PHONE_NUMBER_MATCH_NONE;
374 if (!_e_phone_number_cxx_parse (second_number, region, &pn2, error))
375 return E_PHONE_NUMBER_MATCH_NONE;
376
377 return _e_phone_number_cxx_compare (pn1, pn2);
378 }
379
380 EPhoneNumber *
_e_phone_number_cxx_copy(const EPhoneNumber * phone_number)381 _e_phone_number_cxx_copy (const EPhoneNumber *phone_number)
382 {
383 if (phone_number)
384 return new EPhoneNumber (*phone_number);
385
386 return NULL;
387 }
388
389 void
_e_phone_number_cxx_free(EPhoneNumber * phone_number)390 _e_phone_number_cxx_free (EPhoneNumber *phone_number)
391 {
392 delete phone_number;
393 }
394