1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
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
16 #include "notification_locale.h"
17 #include "hilog_wrapper.h"
18 #include "locale_config.h"
19 #include "locale_matcher.h"
20
21 namespace {
22 constexpr const char *LOCALE_CONFIG_PATH = "/system/etc/peripheral/resources/locale_path.json";
23 constexpr const char *SYSTEM_PERIPHERAL_RESOURCE_PATH = "/system/etc/peripheral/resources/";
24 constexpr const char *ELEMENT_STRING_FILE = "/element/string.json";
25 constexpr const char *DEFAULT_LANGUAGE_EN = "base";
26 } // namespace
27
28 namespace OHOS {
29 namespace ExternalDeviceManager {
30 namespace {
IsPathValid(const std::string & path)31 bool IsPathValid(const std::string &path)
32 {
33 // Check for directory traversal patterns
34 if (path.find("..") != std::string::npos) {
35 return false;
36 }
37
38 // Check for absolute paths
39 if (!path.empty() && (path[0] == '/' || path[0] == '\\')) {
40 return false;
41 }
42
43 // Check for other suspicious characters
44 const std::string forbiddenChars = "\\:*?\"<>|";
45 return path.find_first_of(forbiddenChars) == std::string::npos;
46 }
47 } // namespace
48
49 std::shared_ptr<NotificationLocale> NotificationLocale::instance_ = nullptr;
50 std::mutex NotificationLocale::instanceMutex_;
GetInstance()51 NotificationLocale &NotificationLocale::GetInstance()
52 {
53 std::lock_guard<std::mutex> lock(instanceMutex_);
54 if (instance_ == nullptr) {
55 instance_ = std::make_shared<NotificationLocale>();
56 }
57 return *(instance_.get());
58 }
59
ParseJsonfile(const std::string & targetPath,std::unordered_map<std::string,std::string> & container)60 bool NotificationLocale::ParseJsonfile(
61 const std::string &targetPath, std::unordered_map<std::string, std::string> &container)
62 {
63 if (access(targetPath.c_str(), F_OK) != 0) {
64 EDM_LOGE(MODULE_SERVICE, "targetPath %{public}s invalid", targetPath.c_str());
65 return false;
66 }
67 std::ifstream inputStream(targetPath.c_str(), std::ios::in | std::ios::binary);
68 std::string fileStr(std::istreambuf_iterator<char> {inputStream}, std::istreambuf_iterator<char> {});
69 cJSON *root = cJSON_Parse(fileStr.c_str());
70 if (!root) {
71 EDM_LOGE(MODULE_SERVICE, "%{public}s json parse error", targetPath.c_str());
72 return false;
73 }
74 if (cJSON_IsNull(root) || !cJSON_IsObject(root)) {
75 EDM_LOGE(MODULE_SERVICE, "%{public}s json root error", targetPath.c_str());
76 cJSON_Delete(root);
77 return false;
78 }
79 cJSON *stringConf = cJSON_GetObjectItemCaseSensitive(root, "string");
80 if (!stringConf || cJSON_IsNull(stringConf) || !cJSON_IsArray(stringConf)) {
81 EDM_LOGE(MODULE_SERVICE, "%{public}s stringConf invalid", targetPath.c_str());
82 cJSON_Delete(root);
83 return false;
84 }
85 cJSON *conf = nullptr;
86 cJSON_ArrayForEach(conf, stringConf)
87 {
88 cJSON *nameObj = cJSON_GetObjectItemCaseSensitive(conf, "name");
89 cJSON *valueObj = cJSON_GetObjectItemCaseSensitive(conf, "value");
90 if (nameObj && valueObj && cJSON_IsString(nameObj) && cJSON_IsString(valueObj) &&
91 (strlen(nameObj->valuestring) > 0) && (strlen(valueObj->valuestring) > 0)) {
92 container.insert(std::make_pair(nameObj->valuestring, valueObj->valuestring));
93 }
94 }
95 cJSON_Delete(root);
96 return true;
97 }
98
ParseLocaleCfg()99 void NotificationLocale::ParseLocaleCfg()
100 {
101 if (islanguageMapInit_) {
102 return;
103 }
104 languageMap_.clear();
105 if (ParseJsonfile(LOCALE_CONFIG_PATH, languageMap_)) {
106 islanguageMapInit_ = true;
107 }
108 }
109
UpdateStringMap()110 void NotificationLocale::UpdateStringMap()
111 {
112 std::lock_guard<std::mutex> lock(localeMutex_);
113 OHOS::Global::I18n::LocaleInfo locale(Global::I18n::LocaleConfig::GetSystemLocale());
114 std::string curBaseName = locale.GetBaseName();
115 if (localeBaseName_ == curBaseName) {
116 return;
117 }
118
119 localeBaseName_ = curBaseName;
120 std::string language = DEFAULT_LANGUAGE_EN;
121
122 if (languageMap_.find(localeBaseName_) != languageMap_.end()) {
123 language = languageMap_[localeBaseName_];
124 }
125
126 // Validate language path to prevent directory traversal
127 if (!IsPathValid(language)) {
128 EDM_LOGE(MODULE_SERVICE, "Invalid language path detected: %{public}s", language.c_str());
129 language = DEFAULT_LANGUAGE_EN;
130 }
131
132 stringMap_.clear();
133 std::string resourcePath = SYSTEM_PERIPHERAL_RESOURCE_PATH + language + ELEMENT_STRING_FILE;
134 // Additional path validation
135 if (resourcePath.find("/../") != std::string::npos) {
136 EDM_LOGE(MODULE_SERVICE, "Potential path traversal attack detected");
137 return;
138 }
139 ParseJsonfile(resourcePath, stringMap_);
140 }
141
GetValueByKey(const std::string & key)142 std::string NotificationLocale::GetValueByKey(const std::string &key)
143 {
144 std::lock_guard<std::mutex> lock(localeMutex_);
145 auto iter = stringMap_.find(key);
146 if (iter != stringMap_.end()) {
147 return iter->second;
148 }
149 EDM_LOGE(MODULE_SERVICE, "fail to get related string by key(%{public}s)", key.c_str());
150 return "";
151 }
152 } // namespace ExternalDeviceManager
153 } // namespace OHOS
154