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 ®ion_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 ®ion_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 ®ion_codes);
171 string region_code;
172 GetRegionCodeForShortNumberFromRegionList(number, region_codes, ®ion_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 ®ion_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 ®ion_codes);
367 string region_code;
368 GetRegionCodeForShortNumberFromRegionList(number, region_codes, ®ion_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