1 // Copyright (C) 2012 The Libphonenumber Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "phonenumbers/shortnumberinfo.h"
16
17 #include <algorithm>
18 #include <string.h>
19 #include <iterator>
20 #include <map>
21
22 #include "phonenumbers/default_logger.h"
23 #include "phonenumbers/matcher_api.h"
24 #include "phonenumbers/phonemetadata.pb.h"
25 #include "phonenumbers/phonenumberutil.h"
26 #include "phonenumbers/regex_based_matcher.h"
27 #include "phonenumbers/region_code.h"
28 #include "phonenumbers/short_metadata.h"
29
30 namespace i18n {
31 namespace phonenumbers {
32
33 using google::protobuf::RepeatedField;
34 using std::map;
35 using std::string;
36
LoadCompiledInMetadata(PhoneMetadataCollection * metadata)37 bool LoadCompiledInMetadata(PhoneMetadataCollection* metadata) {
38 if (!metadata->ParseFromArray(short_metadata_get(), short_metadata_size())) {
39 LOG(ERROR) << "Could not parse binary data.";
40 return false;
41 }
42 return true;
43 }
44
ShortNumberInfo()45 ShortNumberInfo::ShortNumberInfo()
46 : phone_util_(*PhoneNumberUtil::GetInstance()),
47 matcher_api_(new RegexBasedMatcher()),
48 region_to_short_metadata_map_(new map<string, PhoneMetadata>()),
49 regions_where_emergency_numbers_must_be_exact_(new set<string>()) {
50 PhoneMetadataCollection metadata_collection;
51 if (!LoadCompiledInMetadata(&metadata_collection)) {
52 LOG(DFATAL) << "Could not parse compiled-in metadata.";
53 return;
54 }
55 for (RepeatedPtrField<PhoneMetadata>::const_iterator it =
56 metadata_collection.metadata().begin();
57 it != metadata_collection.metadata().end();
58 ++it) {
59 const string& region_code = it->id();
60 region_to_short_metadata_map_->insert(std::make_pair(region_code, *it));
61 }
62 regions_where_emergency_numbers_must_be_exact_->insert("BR");
63 regions_where_emergency_numbers_must_be_exact_->insert("CL");
64 regions_where_emergency_numbers_must_be_exact_->insert("NI");
65 }
66
~ShortNumberInfo()67 ShortNumberInfo::~ShortNumberInfo() {}
68
69 // Returns a pointer to the phone metadata for the appropriate region or NULL
70 // if the region code is invalid or unknown.
GetMetadataForRegion(const string & region_code) const71 const PhoneMetadata* ShortNumberInfo::GetMetadataForRegion(
72 const string& region_code) const {
73 map<string, PhoneMetadata>::const_iterator it =
74 region_to_short_metadata_map_->find(region_code);
75 if (it != region_to_short_metadata_map_->end()) {
76 return &it->second;
77 }
78 return NULL;
79 }
80
81 namespace {
82 // TODO: Once we have benchmarked ShortNumberInfo, consider if it is
83 // worth keeping this performance optimization.
MatchesPossibleNumberAndNationalNumber(const MatcherApi & matcher_api,const string & number,const PhoneNumberDesc & desc)84 bool MatchesPossibleNumberAndNationalNumber(
85 const MatcherApi& matcher_api,
86 const string& number,
87 const PhoneNumberDesc& desc) {
88 const RepeatedField<int>& lengths = desc.possible_length();
89 if (desc.possible_length_size() > 0 &&
90 std::find(lengths.begin(), lengths.end(), number.length()) ==
91 lengths.end()) {
92 return false;
93 }
94 return matcher_api.MatchNationalNumber(number, desc, false);
95 }
96 } // namespace
97
98 // Helper method to check that the country calling code of the number matches
99 // the region it's being dialed from.
RegionDialingFromMatchesNumber(const PhoneNumber & number,const string & region_dialing_from) const100 bool ShortNumberInfo::RegionDialingFromMatchesNumber(const PhoneNumber& number,
101 const string& region_dialing_from) const {
102 list<string> region_codes;
103 phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
104 ®ion_codes);
105 return std::find(region_codes.begin(),
106 region_codes.end(),
107 region_dialing_from) != region_codes.end();
108 }
109
IsPossibleShortNumberForRegion(const PhoneNumber & number,const string & region_dialing_from) const110 bool ShortNumberInfo::IsPossibleShortNumberForRegion(const PhoneNumber& number,
111 const string& region_dialing_from) const {
112 if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
113 return false;
114 }
115 const PhoneMetadata* phone_metadata =
116 GetMetadataForRegion(region_dialing_from);
117 if (!phone_metadata) {
118 return false;
119 }
120 string short_number;
121 phone_util_.GetNationalSignificantNumber(number, &short_number);
122 const RepeatedField<int>& lengths =
123 phone_metadata->general_desc().possible_length();
124 return (std::find(lengths.begin(), lengths.end(), short_number.length()) !=
125 lengths.end());
126 }
127
IsPossibleShortNumber(const PhoneNumber & number) const128 bool ShortNumberInfo::IsPossibleShortNumber(const PhoneNumber& number) const {
129 list<string> region_codes;
130 phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
131 ®ion_codes);
132 string short_number;
133 phone_util_.GetNationalSignificantNumber(number, &short_number);
134 for (list<string>::const_iterator it = region_codes.begin();
135 it != region_codes.end(); ++it) {
136 const PhoneMetadata* phone_metadata = GetMetadataForRegion(*it);
137 if (!phone_metadata) {
138 continue;
139 }
140 const RepeatedField<int>& lengths =
141 phone_metadata->general_desc().possible_length();
142 if (std::find(lengths.begin(), lengths.end(), short_number.length()) !=
143 lengths.end()) {
144 return true;
145 }
146 }
147 return false;
148 }
149
IsValidShortNumberForRegion(const PhoneNumber & number,const string & region_dialing_from) const150 bool ShortNumberInfo::IsValidShortNumberForRegion(
151 const PhoneNumber& number, const string& region_dialing_from) const {
152 if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
153 return false;
154 }
155 const PhoneMetadata* phone_metadata =
156 GetMetadataForRegion(region_dialing_from);
157 if (!phone_metadata) {
158 return false;
159 }
160 string short_number;
161 phone_util_.GetNationalSignificantNumber(number, &short_number);
162 const PhoneNumberDesc& general_desc = phone_metadata->general_desc();
163 if (!MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
164 general_desc)) {
165 return false;
166 }
167 const PhoneNumberDesc& short_number_desc = phone_metadata->short_code();
168 return MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
169 short_number_desc);
170 }
171
IsValidShortNumber(const PhoneNumber & number) const172 bool ShortNumberInfo::IsValidShortNumber(const PhoneNumber& number) const {
173 list<string> region_codes;
174 phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
175 ®ion_codes);
176 string region_code;
177 GetRegionCodeForShortNumberFromRegionList(number, region_codes, ®ion_code);
178 if (region_codes.size() > 1 && region_code != RegionCode::GetUnknown()) {
179 return true;
180 }
181 return IsValidShortNumberForRegion(number, region_code);
182 }
183
GetExpectedCostForRegion(const PhoneNumber & number,const string & region_dialing_from) const184 ShortNumberInfo::ShortNumberCost ShortNumberInfo::GetExpectedCostForRegion(
185 const PhoneNumber& number, const string& region_dialing_from) const {
186 if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
187 return ShortNumberInfo::UNKNOWN_COST;
188 }
189 const PhoneMetadata* phone_metadata =
190 GetMetadataForRegion(region_dialing_from);
191 if (!phone_metadata) {
192 return ShortNumberInfo::UNKNOWN_COST;
193 }
194 string short_number;
195 phone_util_.GetNationalSignificantNumber(number, &short_number);
196
197 // The possible lengths are not present for a particular sub-type if they
198 // match the general description; for this reason, we check the possible
199 // lengths against the general description first to allow an early exit if
200 // possible.
201 const RepeatedField<int>& lengths =
202 phone_metadata->general_desc().possible_length();
203 if (std::find(lengths.begin(), lengths.end(), short_number.length()) ==
204 lengths.end()) {
205 return ShortNumberInfo::UNKNOWN_COST;
206 }
207
208 // The cost categories are tested in order of decreasing expense, since if
209 // for some reason the patterns overlap the most expensive matching cost
210 // category should be returned.
211 if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
212 phone_metadata->premium_rate())) {
213 return ShortNumberInfo::PREMIUM_RATE;
214 }
215 if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
216 phone_metadata->standard_rate())) {
217 return ShortNumberInfo::STANDARD_RATE;
218 }
219 if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
220 phone_metadata->toll_free())) {
221 return ShortNumberInfo::TOLL_FREE;
222 }
223 if (IsEmergencyNumber(short_number, region_dialing_from)) {
224 // Emergency numbers are implicitly toll-free.
225 return ShortNumberInfo::TOLL_FREE;
226 }
227 return ShortNumberInfo::UNKNOWN_COST;
228 }
229
GetExpectedCost(const PhoneNumber & number) const230 ShortNumberInfo::ShortNumberCost ShortNumberInfo::GetExpectedCost(
231 const PhoneNumber& number) const {
232 list<string> region_codes;
233 phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
234 ®ion_codes);
235 if (region_codes.size() == 0) {
236 return ShortNumberInfo::UNKNOWN_COST;
237 }
238 if (region_codes.size() == 1) {
239 return GetExpectedCostForRegion(number, region_codes.front());
240 }
241 ShortNumberInfo::ShortNumberCost cost = ShortNumberInfo::TOLL_FREE;
242 for (list<string>::const_iterator it = region_codes.begin();
243 it != region_codes.end(); ++it) {
244 ShortNumberInfo::ShortNumberCost cost_for_region =
245 GetExpectedCostForRegion(number, *it);
246 switch (cost_for_region) {
247 case ShortNumberInfo::PREMIUM_RATE:
248 return ShortNumberInfo::PREMIUM_RATE;
249 case ShortNumberInfo::UNKNOWN_COST:
250 return ShortNumberInfo::UNKNOWN_COST;
251 case ShortNumberInfo::STANDARD_RATE:
252 if (cost != ShortNumberInfo::UNKNOWN_COST) {
253 cost = ShortNumberInfo::STANDARD_RATE;
254 }
255 break;
256 case ShortNumberInfo::TOLL_FREE:
257 // Do nothing.
258 break;
259 default:
260 LOG(ERROR) << "Unrecognised cost for region: "
261 << static_cast<int>(cost_for_region);
262 break;
263 }
264 }
265 return cost;
266 }
267
GetRegionCodeForShortNumberFromRegionList(const PhoneNumber & number,const list<string> & region_codes,string * region_code) const268 void ShortNumberInfo::GetRegionCodeForShortNumberFromRegionList(
269 const PhoneNumber& number, const list<string>& region_codes,
270 string* region_code) const {
271 if (region_codes.size() == 0) {
272 region_code->assign(RegionCode::GetUnknown());
273 return;
274 } else if (region_codes.size() == 1) {
275 region_code->assign(region_codes.front());
276 return;
277 }
278 string national_number;
279 phone_util_.GetNationalSignificantNumber(number, &national_number);
280 for (list<string>::const_iterator it = region_codes.begin();
281 it != region_codes.end(); ++it) {
282 const PhoneMetadata* phone_metadata = GetMetadataForRegion(*it);
283 if (phone_metadata != NULL &&
284 MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
285 phone_metadata->short_code())) {
286 // The number is valid for this region.
287 region_code->assign(*it);
288 return;
289 }
290 }
291 region_code->assign(RegionCode::GetUnknown());
292 }
293
GetExampleShortNumber(const string & region_code) const294 string ShortNumberInfo::GetExampleShortNumber(const string& region_code) const {
295 const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code);
296 if (!phone_metadata) {
297 return "";
298 }
299 const PhoneNumberDesc& desc = phone_metadata->short_code();
300 if (desc.has_example_number()) {
301 return desc.example_number();
302 }
303 return "";
304 }
305
GetExampleShortNumberForCost(const string & region_code,ShortNumberInfo::ShortNumberCost cost) const306 string ShortNumberInfo::GetExampleShortNumberForCost(const string& region_code,
307 ShortNumberInfo::ShortNumberCost cost) const {
308 const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code);
309 if (!phone_metadata) {
310 return "";
311 }
312 const PhoneNumberDesc* desc = NULL;
313 switch (cost) {
314 case TOLL_FREE:
315 desc = &(phone_metadata->toll_free());
316 break;
317 case STANDARD_RATE:
318 desc = &(phone_metadata->standard_rate());
319 break;
320 case PREMIUM_RATE:
321 desc = &(phone_metadata->premium_rate());
322 break;
323 default:
324 // UNKNOWN_COST numbers are computed by the process of elimination from
325 // the other cost categories.
326 break;
327 }
328 if (desc != NULL && desc->has_example_number()) {
329 return desc->example_number();
330 }
331 return "";
332 }
333
ConnectsToEmergencyNumber(const string & number,const string & region_code) const334 bool ShortNumberInfo::ConnectsToEmergencyNumber(const string& number,
335 const string& region_code) const {
336 return MatchesEmergencyNumberHelper(number, region_code,
337 true /* allows prefix match */);
338 }
339
IsEmergencyNumber(const string & number,const string & region_code) const340 bool ShortNumberInfo::IsEmergencyNumber(const string& number,
341 const string& region_code) const {
342 return MatchesEmergencyNumberHelper(number, region_code,
343 false /* doesn't allow prefix match */);
344 }
345
MatchesEmergencyNumberHelper(const string & number,const string & region_code,bool allow_prefix_match) const346 bool ShortNumberInfo::MatchesEmergencyNumberHelper(const string& number,
347 const string& region_code, bool allow_prefix_match) const {
348 string extracted_number;
349 phone_util_.ExtractPossibleNumber(number, &extracted_number);
350 if (phone_util_.StartsWithPlusCharsPattern(extracted_number)) {
351 // Returns false if the number starts with a plus sign. We don't believe
352 // dialing the country code before emergency numbers (e.g. +1911) works,
353 // but later, if that proves to work, we can add additional logic here to
354 // handle it.
355 return false;
356 }
357 const PhoneMetadata* metadata = GetMetadataForRegion(region_code);
358 if (!metadata || !metadata->has_emergency()) {
359 return false;
360 }
361 phone_util_.NormalizeDigitsOnly(&extracted_number);
362 bool allow_prefix_match_for_region =
363 allow_prefix_match &&
364 regions_where_emergency_numbers_must_be_exact_->find(region_code) ==
365 regions_where_emergency_numbers_must_be_exact_->end();
366 return matcher_api_->MatchNationalNumber(
367 extracted_number, metadata->emergency(), allow_prefix_match_for_region);
368 }
369
IsCarrierSpecific(const PhoneNumber & number) const370 bool ShortNumberInfo::IsCarrierSpecific(const PhoneNumber& number) const {
371 list<string> region_codes;
372 phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
373 ®ion_codes);
374 string region_code;
375 GetRegionCodeForShortNumberFromRegionList(number, region_codes, ®ion_code);
376 string national_number;
377 phone_util_.GetNationalSignificantNumber(number, &national_number);
378 const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code);
379 return phone_metadata &&
380 MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
381 phone_metadata->carrier_specific());
382 }
383
IsCarrierSpecificForRegion(const PhoneNumber & number,const string & region_dialing_from) const384 bool ShortNumberInfo::IsCarrierSpecificForRegion(const PhoneNumber& number,
385 const string& region_dialing_from) const {
386 if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
387 return false;
388 }
389 string national_number;
390 phone_util_.GetNationalSignificantNumber(number, &national_number);
391 const PhoneMetadata* phone_metadata =
392 GetMetadataForRegion(region_dialing_from);
393 return phone_metadata &&
394 MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
395 phone_metadata->carrier_specific());
396 }
397
IsSmsServiceForRegion(const PhoneNumber & number,const string & region_dialing_from) const398 bool ShortNumberInfo::IsSmsServiceForRegion(const PhoneNumber& number,
399 const string& region_dialing_from) const {
400 if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
401 return false;
402 }
403 string national_number;
404 phone_util_.GetNationalSignificantNumber(number, &national_number);
405 const PhoneMetadata* phone_metadata =
406 GetMetadataForRegion(region_dialing_from);
407 return phone_metadata &&
408 MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
409 phone_metadata->sms_services());
410 }
411
412 } // namespace phonenumbers
413 } // namespace i18n
414