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 &region,
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