1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "android-base/macros.h"
18 #include "androidfw/Locale.h"
19 #include "androidfw/Util.h"
20 
21 #include <ctype.h>
22 
23 #include <algorithm>
24 #include <string>
25 #include <vector>
26 
27 using ::android::ResTable_config;
28 using ::android::StringPiece;
29 
30 namespace android {
31 
set_language(const char * language_chars)32 void LocaleValue::set_language(const char* language_chars) {
33   size_t i = 0;
34   while ((*language_chars) != '\0') {
35     language[i++] = ::tolower(*language_chars);
36     language_chars++;
37   }
38 }
39 
set_region(const char * region_chars)40 void LocaleValue::set_region(const char* region_chars) {
41   size_t i = 0;
42   while ((*region_chars) != '\0') {
43     region[i++] = ::toupper(*region_chars);
44     region_chars++;
45   }
46 }
47 
set_script(const char * script_chars)48 void LocaleValue::set_script(const char* script_chars) {
49   size_t i = 0;
50   while ((*script_chars) != '\0') {
51     if (i == 0) {
52       script[i++] = ::toupper(*script_chars);
53     } else {
54       script[i++] = ::tolower(*script_chars);
55     }
56     script_chars++;
57   }
58 }
59 
set_variant(const char * variant_chars)60 void LocaleValue::set_variant(const char* variant_chars) {
61   size_t i = 0;
62   while ((*variant_chars) != '\0') {
63     variant[i++] = *variant_chars;
64     variant_chars++;
65   }
66 }
67 
is_alpha(const std::string & str)68 static inline bool is_alpha(const std::string& str) {
69   return std::all_of(std::begin(str), std::end(str), ::isalpha);
70 }
71 
is_number(const std::string & str)72 static inline bool is_number(const std::string& str) {
73   return std::all_of(std::begin(str), std::end(str), ::isdigit);
74 }
75 
InitFromFilterString(const StringPiece & str)76 bool LocaleValue::InitFromFilterString(const StringPiece& str) {
77   // A locale (as specified in the filter) is an underscore separated name such
78   // as "en_US", "en_Latn_US", or "en_US_POSIX".
79   std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
80 
81   const int num_tags = parts.size();
82   bool valid = false;
83   if (num_tags >= 1) {
84     const std::string& lang = parts[0];
85     if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
86       set_language(lang.c_str());
87       valid = true;
88     }
89   }
90 
91   if (!valid || num_tags == 1) {
92     return valid;
93   }
94 
95   // At this point, valid == true && numTags > 1.
96   const std::string& part2 = parts[1];
97   if ((part2.length() == 2 && is_alpha(part2)) ||
98       (part2.length() == 3 && is_number(part2))) {
99     set_region(part2.c_str());
100   } else if (part2.length() == 4 && is_alpha(part2)) {
101     set_script(part2.c_str());
102   } else if (part2.length() >= 4 && part2.length() <= 8) {
103     set_variant(part2.c_str());
104   } else {
105     valid = false;
106   }
107 
108   if (!valid || num_tags == 2) {
109     return valid;
110   }
111 
112   // At this point, valid == true && numTags > 1.
113   const std::string& part3 = parts[2];
114   if (((part3.length() == 2 && is_alpha(part3)) ||
115        (part3.length() == 3 && is_number(part3))) &&
116       script[0]) {
117     set_region(part3.c_str());
118   } else if (part3.length() >= 4 && part3.length() <= 8) {
119     set_variant(part3.c_str());
120   } else {
121     valid = false;
122   }
123 
124   if (!valid || num_tags == 3) {
125     return valid;
126   }
127 
128   const std::string& part4 = parts[3];
129   if (part4.length() >= 4 && part4.length() <= 8) {
130     set_variant(part4.c_str());
131   } else {
132     valid = false;
133   }
134 
135   if (!valid || num_tags > 4) {
136     return false;
137   }
138 
139   return true;
140 }
141 
InitFromBcp47Tag(const StringPiece & bcp47tag)142 bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
143   return InitFromBcp47TagImpl(bcp47tag, '-');
144 }
145 
InitFromBcp47TagImpl(const StringPiece & bcp47tag,const char separator)146 bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
147   std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
148   if (subtags.size() == 1) {
149     set_language(subtags[0].c_str());
150   } else if (subtags.size() == 2) {
151     set_language(subtags[0].c_str());
152 
153     // The second tag can either be a region, a variant or a script.
154     switch (subtags[1].size()) {
155       case 2:
156       case 3:
157         set_region(subtags[1].c_str());
158         break;
159       case 4:
160         if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
161           // This is a variant: fall through
162         } else {
163           set_script(subtags[1].c_str());
164           break;
165         }
166         FALLTHROUGH_INTENDED;
167       case 5:
168       case 6:
169       case 7:
170       case 8:
171         set_variant(subtags[1].c_str());
172         break;
173       default:
174         return false;
175     }
176   } else if (subtags.size() == 3) {
177     // The language is always the first subtag.
178     set_language(subtags[0].c_str());
179 
180     // The second subtag can either be a script or a region code.
181     // If its size is 4, it's a script code, else it's a region code.
182     if (subtags[1].size() == 4) {
183       set_script(subtags[1].c_str());
184     } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
185       set_region(subtags[1].c_str());
186     } else {
187       return false;
188     }
189 
190     // The third tag can either be a region code (if the second tag was
191     // a script), else a variant code.
192     if (subtags[2].size() >= 4) {
193       set_variant(subtags[2].c_str());
194     } else {
195       set_region(subtags[2].c_str());
196     }
197   } else if (subtags.size() == 4) {
198     set_language(subtags[0].c_str());
199     set_script(subtags[1].c_str());
200     set_region(subtags[2].c_str());
201     set_variant(subtags[3].c_str());
202   } else {
203     return false;
204   }
205   return true;
206 }
207 
InitFromParts(std::vector<std::string>::iterator iter,std::vector<std::string>::iterator end)208 ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
209                                    std::vector<std::string>::iterator end) {
210   const std::vector<std::string>::iterator start_iter = iter;
211 
212   std::string& part = *iter;
213   if (part[0] == 'b' && part[1] == '+') {
214     // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
215     // except that the separator is "+" and not "-". Skip the prefix 'b+'.
216     if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) {
217       return -1;
218     }
219     ++iter;
220   } else {
221     if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") {
222       set_language(part.c_str());
223       ++iter;
224 
225       if (iter != end) {
226         const std::string& region_part = *iter;
227         if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
228           set_region(region_part.c_str() + 1);
229           ++iter;
230         }
231       }
232     }
233   }
234   return static_cast<ssize_t>(iter - start_iter);
235 }
236 
InitFromResTable(const ResTable_config & config)237 void LocaleValue::InitFromResTable(const ResTable_config& config) {
238   config.unpackLanguage(language);
239   config.unpackRegion(region);
240   if (config.localeScript[0] && !config.localeScriptWasComputed) {
241     memcpy(script, config.localeScript, sizeof(config.localeScript));
242   }
243 
244   if (config.localeVariant[0]) {
245     memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
246   }
247 }
248 
WriteTo(ResTable_config * out) const249 void LocaleValue::WriteTo(ResTable_config* out) const {
250   out->packLanguage(language);
251   out->packRegion(region);
252 
253   if (script[0]) {
254     memcpy(out->localeScript, script, sizeof(out->localeScript));
255   }
256 
257   if (variant[0]) {
258     memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
259   }
260 }
261 
262 }  // namespace android
263