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 ®ion_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 ®ion_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 ®ion_codes);
180 string region_code;
181 GetRegionCodeForShortNumberFromRegionList(number, region_codes, ®ion_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 ®ion_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 ®ion_codes);
376 string region_code;
377 GetRegionCodeForShortNumberFromRegionList(number, region_codes, ®ion_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