1 /*
2  * Copyright (c) 2022 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 "napi_system_storage.h"
17 
18 #include <string>
19 
20 #include "js_ability.h"
21 #include "js_common_utils.h"
22 #include "preferences_errno.h"
23 #include "preferences_helper.h"
24 
25 using namespace OHOS::NativePreferences;
26 
27 namespace OHOS {
28 namespace PreferencesJsKit {
29 struct AsyncContext {
30     std::string key;
31     std::string def;
32     std::string val;
33     std::string prefName;
34     napi_ref success;
35     napi_ref fail;
36     napi_ref complete;
37     napi_deferred deferred = nullptr;
38     int32_t output = E_ERROR;
39     napi_async_work request;
40 };
41 
42 static constexpr uint32_t MAX_KEY_LENGTH = 32;
43 
44 static constexpr uint32_t MAX_VALUE_LENGTH = 128;
45 
46 static constexpr uint32_t FAILCOUNT = 2;
47 
48 static constexpr uint32_t SUCCOUNT = 1;
49 
50 static constexpr int E_KEY_EMPTY_STSTEM_STORAGE = -1006;
51 
52 static constexpr int E_KEY_EXCEED_LENGTH_LIMIT_STSTEM_STORAGE = -1016;
53 
54 static constexpr int E_VALUE_EXCEED_LENGTH_LIMIT_STSTEM_STORAGE = -1017;
55 
56 static constexpr int E_DEFAULT_EXCEED_LENGTH_LIMIT_STSTEM_STORAGE = -1018;
57 
ParseString(napi_env env,napi_value object,const char * name,const bool enable,std::string & output)58 void ParseString(napi_env env, napi_value object, const char *name, const bool enable, std::string &output)
59 {
60     napi_value value = nullptr;
61     bool exist = false;
62     napi_has_named_property(env, object, name, &exist);
63     if (exist && (napi_get_named_property(env, object, name, &value) == napi_ok)) {
64         std::string key = "";
65         int32_t ret = JSUtils::Convert2NativeValue(env, value, key);
66         NAPI_ASSERT_RETURN_VOID(env, enable || (ret == E_OK && !key.empty()), "StorageOptions is empty.");
67         output = std::move(key);
68     }
69 }
70 
ParseFunction(napi_env env,napi_value object,const char * name,napi_ref & output)71 void ParseFunction(napi_env env, napi_value object, const char *name, napi_ref &output)
72 {
73     napi_value value = nullptr;
74     bool exist = false;
75     napi_has_named_property(env, object, name, &exist);
76     if (exist && (napi_get_named_property(env, object, name, &value) == napi_ok)) {
77         napi_valuetype valueType = napi_null;
78         NAPI_ASSERT_RETURN_VOID(env, value != nullptr, "value == nullptr");
79         NAPI_CALL_RETURN_VOID(env, napi_typeof(env, value, &valueType));
80         NAPI_ASSERT_RETURN_VOID(env, valueType == napi_function, "Wrong argument, function expected.");
81         NAPI_CALL_RETURN_VOID(env, napi_create_reference(env, value, 1, &output));
82     }
83 }
84 
GetMessageInfo(const int & errCode)85 const std::string GetMessageInfo(const int &errCode)
86 {
87     switch (errCode) {
88         case E_KEY_EMPTY:
89             return "The key string is null or empty.";
90         case E_KEY_EXCEED_LENGTH_LIMIT:
91             return "The key string length should shorter than 32.";
92         case E_VALUE_EXCEED_LENGTH_LIMIT:
93             return "The value string length should shorter than 128.";
94         case E_DEFAULT_EXCEED_LENGTH_LIMIT:
95             return "The default string length should shorter than 128.";
96         default:
97             return "unknown err";
98     }
99 }
100 
ConversionToSysStorageErrorCode(const int & errCode)101 int32_t ConversionToSysStorageErrorCode(const int &errCode)
102 {
103     switch (errCode) {
104         case E_KEY_EMPTY:
105             return E_KEY_EMPTY_STSTEM_STORAGE;
106         case E_KEY_EXCEED_LENGTH_LIMIT:
107             return E_KEY_EXCEED_LENGTH_LIMIT_STSTEM_STORAGE;
108         case E_VALUE_EXCEED_LENGTH_LIMIT:
109             return E_VALUE_EXCEED_LENGTH_LIMIT_STSTEM_STORAGE;
110         case E_DEFAULT_EXCEED_LENGTH_LIMIT:
111             return E_DEFAULT_EXCEED_LENGTH_LIMIT_STSTEM_STORAGE;
112         default:
113             return errCode;
114     }
115 }
116 
Complete(napi_env env,napi_status status,void * data)117 void Complete(napi_env env, napi_status status, void *data)
118 {
119     AsyncContext *ctx = static_cast<AsyncContext *>(data);
120     if (status != napi_ok) {
121         napi_throw_type_error(env, nullptr, "Execute callback failed.");
122         return;
123     }
124     size_t len = 0;
125     if (ctx->output == E_OK && ctx->success != nullptr) {
126         napi_value successCallBack = nullptr;
127         NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, ctx->success, &successCallBack));
128         napi_value succRes[SUCCOUNT] = { 0 };
129         len = ctx->val.size();
130         NAPI_CALL_RETURN_VOID(env, napi_create_string_utf8(env, ctx->val.c_str(), len, &succRes[0]));
131         napi_value succCallbackResult = nullptr;
132         NAPI_CALL_RETURN_VOID(
133             env, napi_call_function(env, nullptr, successCallBack, SUCCOUNT, succRes, &succCallbackResult));
134     }
135 
136     if (ctx->output != E_OK && ctx->fail != nullptr) {
137         napi_value failCallBack = nullptr;
138         NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, ctx->fail, &failCallBack));
139         napi_value failRes[FAILCOUNT] = { 0 };
140         std::string message = GetMessageInfo(ctx->output);
141         len = message.size();
142         NAPI_CALL_RETURN_VOID(env, napi_create_string_utf8(env, message.c_str(), len, &failRes[0]));
143         NAPI_CALL_RETURN_VOID(env, napi_create_int32(env, ConversionToSysStorageErrorCode(ctx->output), &failRes[1]));
144         napi_value failCallbackResult = nullptr;
145         NAPI_CALL_RETURN_VOID(
146             env, napi_call_function(env, nullptr, failCallBack, FAILCOUNT, failRes, &failCallbackResult));
147     }
148 
149     if (ctx->complete != nullptr) {
150         napi_value completeCallBack = nullptr;
151         NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, ctx->complete, &completeCallBack));
152         napi_value completeCallbackResult = nullptr;
153         NAPI_CALL_RETURN_VOID(
154             env, napi_call_function(env, nullptr, completeCallBack, 0, nullptr, &completeCallbackResult));
155     }
156     napi_delete_async_work(env, ctx->request);
157     napi_delete_reference(env, ctx->success);
158     napi_delete_reference(env, ctx->fail);
159     napi_delete_reference(env, ctx->complete);
160     napi_value res = nullptr;
161     napi_get_undefined(env, &res);
162     napi_resolve_deferred(env, ctx->deferred, res);
163     delete ctx;
164 }
165 
GetPrefName(napi_env env)166 std::string GetPrefName(napi_env env)
167 {
168     JSAbility::ContextInfo contextInfo;
169     JSAbility::GetContextInfo(env, nullptr, "", contextInfo);
170     return contextInfo.preferencesDir + "/default.xml";
171 }
172 
Operate(napi_env env,napi_callback_info info,const char * resource,bool parseStrFlag,napi_async_execute_callback execute)173 napi_value Operate(napi_env env, napi_callback_info info, const char *resource, bool parseStrFlag,
174     napi_async_execute_callback execute)
175 {
176     size_t argc = 1;
177     napi_value argv[1] = { 0 };
178     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
179     NAPI_ASSERT(env, argc == 1, "Not enough arguments, expected 1.");
180     napi_valuetype valueType = napi_null;
181     NAPI_CALL(env, napi_typeof(env, argv[0], &valueType));
182     NAPI_ASSERT(env, valueType == napi_object, "Wrong argument type, object expected.");
183 
184     AsyncContext *context = new (std::nothrow) AsyncContext();
185     if (context == nullptr) {
186         LOG_ERROR("Operate new failed, context is nullptr");
187         return nullptr;
188     }
189     context->prefName = GetPrefName(env);
190 
191     ParseString(env, argv[0], "key", parseStrFlag, context->key);
192     ParseString(env, argv[0], "value", false, context->val);
193     ParseString(env, argv[0], "default", false, context->def);
194 
195     ParseFunction(env, argv[0], "success", context->success);
196     ParseFunction(env, argv[0], "fail", context->fail);
197     ParseFunction(env, argv[0], "complete", context->complete);
198 
199     napi_value ret = nullptr;
200     napi_value resourceName = nullptr;
201     napi_status status = napi_create_string_utf8(env, resource, NAPI_AUTO_LENGTH, &resourceName);
202     if (status != napi_ok) {
203         LOG_ERROR("Operate get resourceName failed, status = %{public}d", status);
204         delete context;
205         return ret;
206     }
207     status = napi_create_promise(env, &context->deferred, &ret);
208     if (status != napi_ok) {
209         LOG_ERROR("Operate create promise failed, status = %{public}d", status);
210         delete context;
211         return ret;
212     }
213     status = napi_create_async_work(env, nullptr, resourceName, execute, Complete, context, &context->request);
214     if (status != napi_ok) {
215         LOG_ERROR("Operate create asyncWork failed, status = %{public}d", status);
216         delete context;
217         return ret;
218     }
219     status = napi_queue_async_work(env, context->request);
220     if (status != napi_ok) {
221         LOG_ERROR("Operate queue asyncWork failed, status = %{public}d", status);
222         delete context;
223     }
224     return ret;
225 }
226 
NapiGet(napi_env env,napi_callback_info info)227 napi_value NapiGet(napi_env env, napi_callback_info info)
228 {
229     return Operate(env, info, "get", true, [](napi_env env, void *data) {
230         AsyncContext *context = static_cast<AsyncContext *>(data);
231         if (context->key.size() > MAX_KEY_LENGTH) {
232             context->output = E_KEY_EXCEED_LENGTH_LIMIT;
233             return;
234         }
235 
236         if (context->def.size() > MAX_VALUE_LENGTH) {
237             context->output = E_DEFAULT_EXCEED_LENGTH_LIMIT;
238             return;
239         }
240 
241         auto pref = PreferencesHelper::GetPreferences(context->prefName, context->output);
242         context->val = pref->GetString(context->key, context->def);
243     });
244 }
245 
NapiSet(napi_env env,napi_callback_info info)246 napi_value NapiSet(napi_env env, napi_callback_info info)
247 {
248     return Operate(env, info, "set", true, [](napi_env env, void *data) {
249         AsyncContext *context = static_cast<AsyncContext *>(data);
250         if (context->key.size() > MAX_KEY_LENGTH) {
251             context->output = E_KEY_EXCEED_LENGTH_LIMIT;
252             return;
253         }
254         if (context->val.size() > MAX_VALUE_LENGTH) {
255             context->output = E_VALUE_EXCEED_LENGTH_LIMIT;
256             return;
257         }
258 
259         auto pref = PreferencesHelper::GetPreferences(context->prefName, context->output);
260         if (context->output != E_OK) {
261             return;
262         }
263         context->output = pref->PutString(context->key, context->val);
264         pref->FlushSync();
265     });
266 }
267 
NapiDelete(napi_env env,napi_callback_info info)268 napi_value NapiDelete(napi_env env, napi_callback_info info)
269 {
270     return Operate(env, info, "delete", true, [](napi_env env, void *data) {
271         AsyncContext *context = static_cast<AsyncContext *>(data);
272         if (context->key.size() > MAX_KEY_LENGTH) {
273             context->output = E_KEY_EXCEED_LENGTH_LIMIT;
274             return;
275         }
276 
277         auto pref = PreferencesHelper::GetPreferences(context->prefName, context->output);
278         if (context->output != E_OK) {
279             return;
280         }
281         context->output = pref->Delete(context->key);
282         pref->FlushSync();
283     });
284 }
285 
NapiClear(napi_env env,napi_callback_info info)286 napi_value NapiClear(napi_env env, napi_callback_info info)
287 {
288     return Operate(env, info, "clear", false, [](napi_env env, void *data) {
289         AsyncContext *context = static_cast<AsyncContext *>(data);
290         auto pref = PreferencesHelper::GetPreferences(context->prefName, context->output);
291         if (context->output != E_OK) {
292             return;
293         }
294         context->output = pref->Clear();
295         pref->FlushSync();
296     });
297 }
298 
InitSystemStorage(napi_env env,napi_value exports)299 napi_value InitSystemStorage(napi_env env, napi_value exports)
300 {
301     napi_property_descriptor properties[] = {
302         DECLARE_NAPI_FUNCTION("get", NapiGet),
303         DECLARE_NAPI_FUNCTION("delete", NapiDelete),
304         DECLARE_NAPI_FUNCTION("clear", NapiClear),
305         DECLARE_NAPI_FUNCTION("set", NapiSet),
306     };
307     NAPI_CALL(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(*properties), properties));
308     return exports;
309 }
310 } // namespace PreferencesJsKit
311 } // namespace OHOS