• 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 std::map<string, PhoneMetadata>()),
49       regions_where_emergency_numbers_must_be_exact_(new std::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 (const auto& metadata : metadata_collection.metadata()) {
56     const string& region_code = metadata.id();
57     region_to_short_metadata_map_->insert(std::make_pair(region_code, metadata));
58   }
59   regions_where_emergency_numbers_must_be_exact_->insert("BR");
60   regions_where_emergency_numbers_must_be_exact_->insert("CL");
61   regions_where_emergency_numbers_must_be_exact_->insert("NI");
62 }
63 
~ShortNumberInfo()64 ShortNumberInfo::~ShortNumberInfo() {}
65 
66 // Returns a pointer to the phone metadata for the appropriate region or NULL
67 // if the region code is invalid or unknown.
GetMetadataForRegion(const string & region_code) const68 const PhoneMetadata* ShortNumberInfo::GetMetadataForRegion(
69     const string& region_code) const {
70   auto it = region_to_short_metadata_map_->find(region_code);
71   if (it != region_to_short_metadata_map_->end()) {
72     return &it->second;
73   }
74   return nullptr;
75 }
76 
77 namespace {
78 // TODO: Once we have benchmarked ShortNumberInfo, consider if it is
79 // worth keeping this performance optimization.
MatchesPossibleNumberAndNationalNumber(const MatcherApi & matcher_api,const string & number,const PhoneNumberDesc & desc)80 bool MatchesPossibleNumberAndNationalNumber(
81     const MatcherApi& matcher_api,
82     const string& number,
83     const PhoneNumberDesc& desc) {
84   const RepeatedField<int>& lengths = desc.possible_length();
85   if (desc.possible_length_size() > 0 &&
86       std::find(lengths.begin(), lengths.end(), number.length()) ==
87           lengths.end()) {
88     return false;
89   }
90   return matcher_api.MatchNationalNumber(number, desc, false);
91 }
92 }  // namespace
93 
94 // Helper method to check that the country calling code of the number matches
95 // the region it's being dialed from.
RegionDialingFromMatchesNumber(const PhoneNumber & number,const string & region_dialing_from) const96 bool ShortNumberInfo::RegionDialingFromMatchesNumber(const PhoneNumber& number,
97     const string& region_dialing_from) const {
98   list<string> region_codes;
99   phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
100                                                   &region_codes);
101   return std::find(region_codes.begin(),
102                    region_codes.end(),
103                    region_dialing_from) != region_codes.end();
104 }
105 
IsPossibleShortNumberForRegion(const PhoneNumber & number,const string & region_dialing_from) const106 bool ShortNumberInfo::IsPossibleShortNumberForRegion(const PhoneNumber& number,
107     const string& region_dialing_from) const {
108   if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
109     return false;
110   }
111   const PhoneMetadata* phone_metadata =
112       GetMetadataForRegion(region_dialing_from);
113   if (!phone_metadata) {
114     return false;
115   }
116   string short_number;
117   phone_util_.GetNationalSignificantNumber(number, &short_number);
118   const RepeatedField<int>& lengths =
119       phone_metadata->general_desc().possible_length();
120   return (std::find(lengths.begin(), lengths.end(), short_number.length()) !=
121       lengths.end());
122 }
123 
IsPossibleShortNumber(const PhoneNumber & number) const124 bool ShortNumberInfo::IsPossibleShortNumber(const PhoneNumber& number) const {
125   list<string> region_codes;
126   phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
127                                                   &region_codes);
128   string short_number;
129   phone_util_.GetNationalSignificantNumber(number, &short_number);
130   for (const auto& region_code : region_codes) {
131     const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code);
132     if (!phone_metadata) {
133       continue;
134     }
135     const RepeatedField<int>& lengths =
136         phone_metadata->general_desc().possible_length();
137     if (std::find(lengths.begin(), lengths.end(), short_number.length()) !=
138         lengths.end()) {
139       return true;
140     }
141   }
142   return false;
143 }
144 
IsValidShortNumberForRegion(const PhoneNumber & number,const string & region_dialing_from) const145 bool ShortNumberInfo::IsValidShortNumberForRegion(
146     const PhoneNumber& number, const string& region_dialing_from) const {
147   if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
148     return false;
149   }
150   const PhoneMetadata* phone_metadata =
151       GetMetadataForRegion(region_dialing_from);
152   if (!phone_metadata) {
153     return false;
154   }
155   string short_number;
156   phone_util_.GetNationalSignificantNumber(number, &short_number);
157   const PhoneNumberDesc& general_desc = phone_metadata->general_desc();
158   if (!MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
159                                               general_desc)) {
160     return false;
161   }
162   const PhoneNumberDesc& short_number_desc = phone_metadata->short_code();
163   return MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
164                                                 short_number_desc);
165 }
166 
IsValidShortNumber(const PhoneNumber & number) const167 bool ShortNumberInfo::IsValidShortNumber(const PhoneNumber& number) const {
168   list<string> region_codes;
169   phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
170                                                   &region_codes);
171   string region_code;
172   GetRegionCodeForShortNumberFromRegionList(number, region_codes, &region_code);
173   if (region_codes.size() > 1 && region_code != RegionCode::GetUnknown()) {
174     return true;
175   }
176   return IsValidShortNumberForRegion(number, region_code);
177 }
178 
GetExpectedCostForRegion(const PhoneNumber & number,const string & region_dialing_from) const179 ShortNumberInfo::ShortNumberCost ShortNumberInfo::GetExpectedCostForRegion(
180     const PhoneNumber& number, const string& region_dialing_from) const {
181   if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
182     return ShortNumberInfo::UNKNOWN_COST;
183   }
184   const PhoneMetadata* phone_metadata =
185       GetMetadataForRegion(region_dialing_from);
186   if (!phone_metadata) {
187     return ShortNumberInfo::UNKNOWN_COST;
188   }
189   string short_number;
190   phone_util_.GetNationalSignificantNumber(number, &short_number);
191 
192   // The possible lengths are not present for a particular sub-type if they
193   // match the general description; for this reason, we check the possible
194   // lengths against the general description first to allow an early exit if
195   // possible.
196   const RepeatedField<int>& lengths =
197       phone_metadata->general_desc().possible_length();
198   if (std::find(lengths.begin(), lengths.end(), short_number.length()) ==
199       lengths.end()) {
200     return ShortNumberInfo::UNKNOWN_COST;
201   }
202 
203   // The cost categories are tested in order of decreasing expense, since if
204   // for some reason the patterns overlap the most expensive matching cost
205   // category should be returned.
206   if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
207                                              phone_metadata->premium_rate())) {
208     return ShortNumberInfo::PREMIUM_RATE;
209   }
210   if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
211                                              phone_metadata->standard_rate())) {
212     return ShortNumberInfo::STANDARD_RATE;
213   }
214   if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
215                                              phone_metadata->toll_free())) {
216     return ShortNumberInfo::TOLL_FREE;
217   }
218   if (IsEmergencyNumber(short_number, region_dialing_from)) {
219     // Emergency numbers are implicitly toll-free.
220     return ShortNumberInfo::TOLL_FREE;
221   }
222   return ShortNumberInfo::UNKNOWN_COST;
223 }
224 
GetExpectedCost(const PhoneNumber & number) const225 ShortNumberInfo::ShortNumberCost ShortNumberInfo::GetExpectedCost(
226     const PhoneNumber& number) const {
227   list<string> region_codes;
228   phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
229                                                   &region_codes);
230   if (region_codes.size() == 0) {
231     return ShortNumberInfo::UNKNOWN_COST;
232   }
233   if (region_codes.size() == 1) {
234     return GetExpectedCostForRegion(number, region_codes.front());
235   }
236   ShortNumberInfo::ShortNumberCost cost = ShortNumberInfo::TOLL_FREE;
237   for (const auto& region_code : region_codes) {
238     ShortNumberInfo::ShortNumberCost cost_for_region =
239         GetExpectedCostForRegion(number, region_code);
240     switch (cost_for_region) {
241      case ShortNumberInfo::PREMIUM_RATE:
242        return ShortNumberInfo::PREMIUM_RATE;
243      case ShortNumberInfo::UNKNOWN_COST:
244        return ShortNumberInfo::UNKNOWN_COST;
245      case ShortNumberInfo::STANDARD_RATE:
246        if (cost != ShortNumberInfo::UNKNOWN_COST) {
247          cost = ShortNumberInfo::STANDARD_RATE;
248        }
249        break;
250      case ShortNumberInfo::TOLL_FREE:
251        // Do nothing.
252        break;
253      default:
254        LOG(ERROR) << "Unrecognised cost for region: "
255                   << static_cast<int>(cost_for_region);
256        break;
257     }
258   }
259   return cost;
260 }
261 
GetRegionCodeForShortNumberFromRegionList(const PhoneNumber & number,const list<string> & region_codes,string * region_code) const262 void ShortNumberInfo::GetRegionCodeForShortNumberFromRegionList(
263     const PhoneNumber& number, const list<string>& region_codes,
264     string* region_code) const {
265   if (region_codes.size() == 0) {
266     region_code->assign(RegionCode::GetUnknown());
267     return;
268   } else if (region_codes.size() == 1) {
269     region_code->assign(region_codes.front());
270     return;
271   }
272   string national_number;
273   phone_util_.GetNationalSignificantNumber(number, &national_number);
274   for (const auto& region_code_it : region_codes) {
275     const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code_it);
276     if (phone_metadata != nullptr &&
277         MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
278                                                phone_metadata->short_code())) {
279       // The number is valid for this region.
280       region_code->assign(region_code_it);
281       return;
282     }
283   }
284   region_code->assign(RegionCode::GetUnknown());
285 }
286 
GetExampleShortNumber(const string & region_code) const287 string ShortNumberInfo::GetExampleShortNumber(const string& region_code) const {
288   const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code);
289   if (!phone_metadata) {
290     return "";
291   }
292   const PhoneNumberDesc& desc = phone_metadata->short_code();
293   if (desc.has_example_number()) {
294     return desc.example_number();
295   }
296   return "";
297 }
298 
GetExampleShortNumberForCost(const string & region_code,ShortNumberInfo::ShortNumberCost cost) const299 string ShortNumberInfo::GetExampleShortNumberForCost(const string& region_code,
300     ShortNumberInfo::ShortNumberCost cost) const {
301   const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code);
302   if (!phone_metadata) {
303     return "";
304   }
305   const PhoneNumberDesc* desc = nullptr;
306   switch (cost) {
307     case TOLL_FREE:
308       desc = &(phone_metadata->toll_free());
309       break;
310     case STANDARD_RATE:
311       desc = &(phone_metadata->standard_rate());
312       break;
313     case PREMIUM_RATE:
314       desc = &(phone_metadata->premium_rate());
315       break;
316     default:
317       // UNKNOWN_COST numbers are computed by the process of elimination from
318       // the other cost categories.
319       break;
320   }
321   if (desc != nullptr && desc->has_example_number()) {
322     return desc->example_number();
323   }
324   return "";
325 }
326 
ConnectsToEmergencyNumber(const string & number,const string & region_code) const327 bool ShortNumberInfo::ConnectsToEmergencyNumber(const string& number,
328     const string& region_code) const {
329   return MatchesEmergencyNumberHelper(number, region_code,
330       true /* allows prefix match */);
331 }
332 
IsEmergencyNumber(const string & number,const string & region_code) const333 bool ShortNumberInfo::IsEmergencyNumber(const string& number,
334     const string& region_code) const {
335   return MatchesEmergencyNumberHelper(number, region_code,
336       false /* doesn't allow prefix match */);
337 }
338 
MatchesEmergencyNumberHelper(const string & number,const string & region_code,bool allow_prefix_match) const339 bool ShortNumberInfo::MatchesEmergencyNumberHelper(const string& number,
340     const string& region_code, bool allow_prefix_match) const {
341   string extracted_number;
342   phone_util_.ExtractPossibleNumber(number, &extracted_number);
343   if (phone_util_.StartsWithPlusCharsPattern(extracted_number)) {
344     // Returns false if the number starts with a plus sign. We don't believe
345     // dialing the country code before emergency numbers (e.g. +1911) works,
346     // but later, if that proves to work, we can add additional logic here to
347     // handle it.
348     return false;
349   }
350   const PhoneMetadata* metadata = GetMetadataForRegion(region_code);
351   if (!metadata || !metadata->has_emergency()) {
352     return false;
353   }
354   phone_util_.NormalizeDigitsOnly(&extracted_number);
355   bool allow_prefix_match_for_region =
356       allow_prefix_match &&
357       regions_where_emergency_numbers_must_be_exact_->find(region_code) ==
358           regions_where_emergency_numbers_must_be_exact_->end();
359   return matcher_api_->MatchNationalNumber(
360       extracted_number, metadata->emergency(), allow_prefix_match_for_region);
361 }
362 
IsCarrierSpecific(const PhoneNumber & number) const363 bool ShortNumberInfo::IsCarrierSpecific(const PhoneNumber& number) const {
364   list<string> region_codes;
365   phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
366                                                   &region_codes);
367   string region_code;
368   GetRegionCodeForShortNumberFromRegionList(number, region_codes, &region_code);
369   string national_number;
370   phone_util_.GetNationalSignificantNumber(number, &national_number);
371   const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code);
372   return phone_metadata &&
373          MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
374              phone_metadata->carrier_specific());
375 }
376 
IsCarrierSpecificForRegion(const PhoneNumber & number,const string & region_dialing_from) const377 bool ShortNumberInfo::IsCarrierSpecificForRegion(const PhoneNumber& number,
378     const string& region_dialing_from) const {
379   if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
380     return false;
381   }
382   string national_number;
383   phone_util_.GetNationalSignificantNumber(number, &national_number);
384   const PhoneMetadata* phone_metadata =
385       GetMetadataForRegion(region_dialing_from);
386   return phone_metadata &&
387          MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
388              phone_metadata->carrier_specific());
389 }
390 
IsSmsServiceForRegion(const PhoneNumber & number,const string & region_dialing_from) const391 bool ShortNumberInfo::IsSmsServiceForRegion(const PhoneNumber& number,
392     const string& region_dialing_from) const {
393   if (!RegionDialingFromMatchesNumber(number, region_dialing_from)) {
394     return false;
395   }
396   string national_number;
397   phone_util_.GetNationalSignificantNumber(number, &national_number);
398   const PhoneMetadata* phone_metadata =
399       GetMetadataForRegion(region_dialing_from);
400   return phone_metadata &&
401          MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
402              phone_metadata->sms_services());
403 }
404 
405 }  // namespace phonenumbers
406 }  // namespace i18n
407