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