• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                                                   &region_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                                                   &region_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                                                   &region_codes);
176   string region_code;
177   GetRegionCodeForShortNumberFromRegionList(number, region_codes, &region_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                                                   &region_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                                                   &region_codes);
374   string region_code;
375   GetRegionCodeForShortNumberFromRegionList(number, region_codes, &region_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