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
32 template <size_t N, class Transformer>
safe_transform_copy(const char * source,char (& dest)[N],Transformer t)33 static void safe_transform_copy(const char* source, char (&dest)[N], Transformer t) {
34 size_t i = 0;
35 while (i < N && (*source) != '\0') {
36 dest[i++] = t(i, *source);
37 source++;
38 }
39 while (i < N) {
40 dest[i++] = '\0';
41 }
42 }
43
set_language(const char * language_chars)44 void LocaleValue::set_language(const char* language_chars) {
45 safe_transform_copy(language_chars, language, [](size_t, char c) { return ::tolower(c); });
46 }
47
set_region(const char * region_chars)48 void LocaleValue::set_region(const char* region_chars) {
49 safe_transform_copy(region_chars, region, [](size_t, char c) { return ::toupper(c); });
50 }
51
set_script(const char * script_chars)52 void LocaleValue::set_script(const char* script_chars) {
53 safe_transform_copy(script_chars, script,
54 [](size_t i, char c) { return i ? ::tolower(c) : ::toupper(c); });
55 }
56
set_variant(const char * variant_chars)57 void LocaleValue::set_variant(const char* variant_chars) {
58 safe_transform_copy(variant_chars, variant, [](size_t, char c) { return c; });
59 }
60
is_alpha(const std::string & str)61 static inline bool is_alpha(const std::string& str) {
62 return std::all_of(std::begin(str), std::end(str), ::isalpha);
63 }
64
is_number(const std::string & str)65 static inline bool is_number(const std::string& str) {
66 return std::all_of(std::begin(str), std::end(str), ::isdigit);
67 }
68
InitFromFilterString(StringPiece str)69 bool LocaleValue::InitFromFilterString(StringPiece str) {
70 // A locale (as specified in the filter) is an underscore separated name such
71 // as "en_US", "en_Latn_US", or "en_US_POSIX".
72 std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
73
74 const int num_tags = parts.size();
75 bool valid = false;
76 if (num_tags >= 1) {
77 const std::string& lang = parts[0];
78 if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
79 set_language(lang.c_str());
80 valid = true;
81 }
82 }
83
84 if (!valid || num_tags == 1) {
85 return valid;
86 }
87
88 // At this point, valid == true && numTags > 1.
89 const std::string& part2 = parts[1];
90 if ((part2.length() == 2 && is_alpha(part2)) ||
91 (part2.length() == 3 && is_number(part2))) {
92 set_region(part2.c_str());
93 } else if (part2.length() == 4 && is_alpha(part2)) {
94 set_script(part2.c_str());
95 } else if (part2.length() >= 4 && part2.length() <= 8) {
96 set_variant(part2.c_str());
97 } else {
98 valid = false;
99 }
100
101 if (!valid || num_tags == 2) {
102 return valid;
103 }
104
105 // At this point, valid == true && numTags > 1.
106 const std::string& part3 = parts[2];
107 if (((part3.length() == 2 && is_alpha(part3)) ||
108 (part3.length() == 3 && is_number(part3))) &&
109 script[0]) {
110 set_region(part3.c_str());
111 } else if (part3.length() >= 4 && part3.length() <= 8) {
112 set_variant(part3.c_str());
113 } else {
114 valid = false;
115 }
116
117 if (!valid || num_tags == 3) {
118 return valid;
119 }
120
121 const std::string& part4 = parts[3];
122 if (part4.length() >= 4 && part4.length() <= 8) {
123 set_variant(part4.c_str());
124 } else {
125 valid = false;
126 }
127
128 if (!valid || num_tags > 4) {
129 return false;
130 }
131
132 return true;
133 }
134
InitFromBcp47Tag(StringPiece bcp47tag)135 bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) {
136 return InitFromBcp47TagImpl(bcp47tag, '-');
137 }
138
InitFromBcp47TagImpl(StringPiece bcp47tag,const char separator)139 bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) {
140 std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
141 if (subtags.size() == 1) {
142 set_language(subtags[0].c_str());
143 } else if (subtags.size() == 2) {
144 set_language(subtags[0].c_str());
145
146 // The second tag can either be a region, a variant or a script.
147 switch (subtags[1].size()) {
148 case 2:
149 case 3:
150 set_region(subtags[1].c_str());
151 break;
152 case 4:
153 if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
154 // This is a variant: fall through
155 } else {
156 set_script(subtags[1].c_str());
157 break;
158 }
159 FALLTHROUGH_INTENDED;
160 case 5:
161 case 6:
162 case 7:
163 case 8:
164 set_variant(subtags[1].c_str());
165 break;
166 default:
167 return false;
168 }
169 } else if (subtags.size() == 3) {
170 // The language is always the first subtag.
171 set_language(subtags[0].c_str());
172
173 // The second subtag can either be a script or a region code.
174 // If its size is 4, it's a script code, else it's a region code.
175 if (subtags[1].size() == 4) {
176 set_script(subtags[1].c_str());
177 } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
178 set_region(subtags[1].c_str());
179 } else {
180 return false;
181 }
182
183 // The third tag can either be a region code (if the second tag was
184 // a script), else a variant code.
185 if (subtags[2].size() >= 4) {
186 set_variant(subtags[2].c_str());
187 } else {
188 set_region(subtags[2].c_str());
189 }
190 } else if (subtags.size() == 4) {
191 set_language(subtags[0].c_str());
192 set_script(subtags[1].c_str());
193 set_region(subtags[2].c_str());
194 set_variant(subtags[3].c_str());
195 } else {
196 return false;
197 }
198 return true;
199 }
200
InitFromParts(std::vector<std::string>::iterator iter,std::vector<std::string>::iterator end)201 ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
202 std::vector<std::string>::iterator end) {
203 const std::vector<std::string>::iterator start_iter = iter;
204
205 std::string& part = *iter;
206 if (part[0] == 'b' && part[1] == '+') {
207 // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
208 // except that the separator is "+" and not "-". Skip the prefix 'b+'.
209 if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) {
210 return -1;
211 }
212 ++iter;
213 } else {
214 if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") {
215 set_language(part.c_str());
216 ++iter;
217
218 if (iter != end) {
219 const std::string& region_part = *iter;
220 if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
221 set_region(region_part.c_str() + 1);
222 ++iter;
223 }
224 }
225 }
226 }
227 return static_cast<ssize_t>(iter - start_iter);
228 }
229
230 // Make sure the following memcpy's are properly sized.
231 static_assert(sizeof(ResTable_config::localeScript) == sizeof(LocaleValue::script));
232 static_assert(sizeof(ResTable_config::localeVariant) == sizeof(LocaleValue::variant));
233
InitFromResTable(const ResTable_config & config)234 void LocaleValue::InitFromResTable(const ResTable_config& config) {
235 config.unpackLanguage(language);
236 config.unpackRegion(region);
237 if (config.localeScript[0] && !config.localeScriptWasComputed) {
238 memcpy(script, config.localeScript, sizeof(config.localeScript));
239 }
240
241 if (config.localeVariant[0]) {
242 memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
243 }
244 }
245
WriteTo(ResTable_config * out) const246 void LocaleValue::WriteTo(ResTable_config* out) const {
247 out->packLanguage(language);
248 out->packRegion(region);
249
250 if (script[0]) {
251 memcpy(out->localeScript, script, sizeof(out->localeScript));
252 }
253
254 if (variant[0]) {
255 memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
256 }
257 }
258
259 } // namespace android
260