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 "upload/js_util.h"
17 
18 #include <securec.h>
19 
20 #include <regex>
21 #include <string>
22 
23 #include "napi_utils.h"
24 
25 namespace OHOS::Request::Upload {
26 
27 static const std::map<ExceptionErrorCode, std::string> ErrorCodeToMsg{
28     { E_OK, E_OK_INFO },
29     { E_PERMISSION, E_PERMISSION_INFO },
30     { E_PARAMETER_CHECK, E_PARAMETER_CHECK_INFO },
31     { E_UNSUPPORTED, E_UNSUPPORTED_INFO },
32     { E_FILE_IO, E_FILE_IO_INFO },
33     { E_FILE_PATH, E_FILE_PATH_INFO },
34     { E_SERVICE_ERROR, E_SERVICE_ERROR_INFO },
35     { E_OTHER, E_OTHER_INFO },
36 };
37 
Convert2String(napi_env env,napi_value jsString)38 std::string JSUtil::Convert2String(napi_env env, napi_value jsString)
39 {
40     size_t maxLen = JSUtil::MAX_LEN;
41     napi_status status = napi_get_value_string_utf8(env, jsString, NULL, 0, &maxLen);
42     if (status != napi_ok) {
43         GET_AND_THROW_LAST_ERROR((env));
44         maxLen = JSUtil::MAX_LEN;
45     }
46     if (maxLen == 0) {
47         return std::string();
48     }
49     char *buf = new char[maxLen + 1];
50     if (buf == nullptr) {
51         return std::string();
52     }
53     size_t len = 0;
54     status = napi_get_value_string_utf8(env, jsString, buf, maxLen + 1, &len);
55     if (status != napi_ok) {
56         GET_AND_THROW_LAST_ERROR((env));
57     }
58     buf[len] = 0;
59     std::string value(buf);
60     delete[] buf;
61     return value;
62 }
63 
Convert2JSString(napi_env env,const std::string & cString)64 napi_value JSUtil::Convert2JSString(napi_env env, const std::string &cString)
65 {
66     napi_value jsValue = nullptr;
67     napi_create_string_utf8(env, cString.c_str(), cString.size(), &jsValue);
68     return jsValue;
69 }
70 
Convert2JSValue(napi_env env,int32_t value)71 napi_value JSUtil::Convert2JSValue(napi_env env, int32_t value)
72 {
73     napi_value jsValue;
74     napi_status status = napi_create_int32(env, value, &jsValue);
75     if (status != napi_ok) {
76         return nullptr;
77     }
78     return jsValue;
79 }
80 
Convert2JSUploadResponse(napi_env env,const Upload::UploadResponse & response)81 napi_value JSUtil::Convert2JSUploadResponse(napi_env env, const Upload::UploadResponse &response)
82 {
83     napi_value jsResponse = nullptr;
84     napi_create_object(env, &jsResponse);
85     napi_set_named_property(env, jsResponse, "code", Convert2JSValue(env, response.code));
86     napi_set_named_property(env, jsResponse, "data", Convert2JSString(env, response.data));
87     napi_set_named_property(env, jsResponse, "headers", Convert2JSString(env, response.headers));
88     return jsResponse;
89 }
90 
ParseFunction(napi_env env,napi_value & object,const char * name,napi_ref & output)91 bool JSUtil::ParseFunction(napi_env env, napi_value &object, const char *name, napi_ref &output)
92 {
93     napi_value value = GetNamedProperty(env, object, name);
94     if (value == nullptr) {
95         return false;
96     }
97     napi_valuetype valueType = napi_null;
98     auto ret = napi_typeof(env, value, &valueType);
99     if ((ret != napi_ok) || (valueType != napi_function)) {
100         return false;
101     }
102     napi_create_reference(env, value, 1, &output);
103     return true;
104 }
105 
ParseUploadConfig(napi_env env,napi_value jsConfig,const std::string & version)106 std::shared_ptr<UploadConfig> JSUtil::ParseUploadConfig(napi_env env, napi_value jsConfig, const std::string &version)
107 {
108     UPLOAD_HILOGD(UPLOAD_MODULE_JS_NAPI, "ParseUploadConfig in");
109     UploadConfig config;
110     config.protocolVersion = version;
111     bool ret = ToUploadOption(env, jsConfig, config);
112     if ((!ret) || (!CheckConfig(config))) {
113         return nullptr;
114     }
115     return std::make_shared<UploadConfig>(config);
116 }
117 
CheckConfig(const UploadConfig & config)118 bool JSUtil::CheckConfig(const UploadConfig &config)
119 {
120     if (!CheckUrl(config.url)) {
121         return false;
122     }
123     if (config.files.empty()) {
124         return false;
125     }
126     return CheckMethod(config.method);
127 }
128 
CheckUrl(const std::string & url)129 bool JSUtil::CheckUrl(const std::string &url)
130 {
131     if (url.empty()) {
132         return false;
133     }
134     return regex_match(url, std::regex("^http(s)?:\\/\\/.+"));
135 }
136 
CheckMethod(const std::string & method)137 bool JSUtil::CheckMethod(const std::string &method)
138 {
139     return (method == POST || method == PUT);
140 }
141 
GetNamedProperty(napi_env env,napi_value object,const std::string & propertyName)142 napi_value JSUtil::GetNamedProperty(napi_env env, napi_value object, const std::string &propertyName)
143 {
144     napi_value value = nullptr;
145     bool hasProperty = false;
146     NAPI_CALL(env, napi_has_named_property(env, object, propertyName.c_str(), &hasProperty));
147     if (!hasProperty) {
148         return value;
149     }
150     NAPI_CALL(env, napi_get_named_property(env, object, propertyName.c_str(), &value));
151     return value;
152 }
153 
HasNamedProperty(napi_env env,napi_value object,const std::string & propertyName)154 bool JSUtil::HasNamedProperty(napi_env env, napi_value object, const std::string &propertyName)
155 {
156     bool hasProperty = false;
157     NAPI_CALL_BASE(env, napi_has_named_property(env, object, propertyName.c_str(), &hasProperty), false);
158     return hasProperty;
159 }
160 
SetData(napi_env env,napi_value jsConfig,UploadConfig & config)161 bool JSUtil::SetData(napi_env env, napi_value jsConfig, UploadConfig &config)
162 {
163     if (!HasNamedProperty(env, jsConfig, "data")) {
164         return true;
165     }
166     napi_value data = nullptr;
167     napi_get_named_property(env, jsConfig, "data", &data);
168     if (data == nullptr) {
169         UPLOAD_HILOGE(UPLOAD_MODULE_JS_NAPI, "GetNamedProperty SetData failed");
170         return false;
171     }
172     config.data = Convert2RequestDataVector(env, data);
173     return true;
174 }
175 
SetFiles(napi_env env,napi_value jsConfig,UploadConfig & config)176 bool JSUtil::SetFiles(napi_env env, napi_value jsConfig, UploadConfig &config)
177 {
178     napi_value files = GetNamedProperty(env, jsConfig, "files");
179     if (files == nullptr) {
180         UPLOAD_HILOGE(UPLOAD_MODULE_JS_NAPI, "GetNamedProperty SetFiles failed");
181         return false;
182     }
183     config.files = Convert2FileVector(env, files, config.protocolVersion);
184     return true;
185 }
186 
ToUploadOption(napi_env env,napi_value jsConfig,UploadConfig & config)187 bool JSUtil::ToUploadOption(napi_env env, napi_value jsConfig, UploadConfig &config)
188 {
189     if (!SetMandatoryParam(env, jsConfig, "url", config.url)) {
190         return false;
191     }
192     if (!SetData(env, jsConfig, config)) {
193         return false;
194     }
195     if (!SetFiles(env, jsConfig, config)) {
196         return false;
197     }
198     if (!ParseHeader(env, jsConfig, config.header)) {
199         return false;
200     }
201     if (!SetOptionalParam(env, jsConfig, "method", config.method)) {
202         return false;
203     }
204     return true;
205 }
206 
ParseHeader(napi_env env,napi_value configValue,std::map<std::string,std::string> & header)207 bool JSUtil::ParseHeader(napi_env env, napi_value configValue, std::map<std::string, std::string> &header)
208 {
209     if (!NapiUtils::HasNamedProperty(env, configValue, "header")) {
210         UPLOAD_HILOGE(UPLOAD_MODULE_JS_NAPI, "No header present, Reassign value");
211         header[tlsVersion] = TLS_VERSION;
212         header[cipherList] = TLS_CIPHER;
213         return true;
214     }
215     napi_value jsHeader = NapiUtils::GetNamedProperty(env, configValue, "header");
216     if (NapiUtils::GetValueType(env, jsHeader) != napi_object) {
217         return false;
218     }
219     auto names = NapiUtils::GetPropertyNames(env, jsHeader);
220     auto iter = find(names.begin(), names.end(), cipherList);
221     if (iter == names.end()) {
222         header[cipherList] = TLS_CIPHER;
223     }
224     for (iter = names.begin(); iter != names.end(); ++iter) {
225         auto value = NapiUtils::Convert2String(env, jsHeader, *iter);
226         if (!value.empty()) {
227             header[*iter] = value;
228         }
229     }
230     return true;
231 }
232 
SetMandatoryParam(napi_env env,napi_value jsValue,const std::string & str,std::string & out)233 bool JSUtil::SetMandatoryParam(napi_env env, napi_value jsValue, const std::string &str, std::string &out)
234 {
235     napi_value value = GetNamedProperty(env, jsValue, str);
236     if (value == nullptr) {
237         UPLOAD_HILOGE(UPLOAD_MODULE_JS_NAPI, "SetMandatoryParam failed");
238         return false;
239     }
240     out = Convert2String(env, value);
241     return true;
242 }
243 
SetOptionalParam(napi_env env,napi_value jsValue,const std::string & str,std::string & out)244 bool JSUtil::SetOptionalParam(napi_env env, napi_value jsValue, const std::string &str, std::string &out)
245 {
246     if (!HasNamedProperty(env, jsValue, str)) {
247         out = (str == "method" ? "POST" : "");
248         return true;
249     }
250     napi_value value = nullptr;
251     napi_get_named_property(env, jsValue, str.c_str(), &value);
252     if (value == nullptr) {
253         UPLOAD_HILOGE(UPLOAD_MODULE_JS_NAPI, "SetOptionalParam failed");
254         return false;
255     }
256     out = Convert2String(env, value);
257     return true;
258 }
259 
Convert2FileL5(napi_env env,napi_value jsFile,Upload::File & file)260 bool JSUtil::Convert2FileL5(napi_env env, napi_value jsFile, Upload::File &file)
261 {
262     if (!SetOptionalParam(env, jsFile, "filename", file.filename)) {
263         return false;
264     }
265     if (!SetOptionalParam(env, jsFile, "name", file.name)) {
266         return false;
267     }
268     if (!SetMandatoryParam(env, jsFile, "uri", file.uri)) {
269         return false;
270     }
271     if (!SetOptionalParam(env, jsFile, "type", file.type)) {
272         return false;
273     }
274     return true;
275 }
276 
Convert2FileVector(napi_env env,napi_value jsFiles,const std::string & version)277 std::vector<Upload::File> JSUtil::Convert2FileVector(napi_env env, napi_value jsFiles, const std::string &version)
278 {
279     bool isArray = false;
280     napi_is_array(env, jsFiles, &isArray);
281     NAPI_ASSERT_BASE(env, isArray, "not array", {});
282     uint32_t length = 0;
283     napi_get_array_length(env, jsFiles, &length);
284     std::vector<Upload::File> files;
285     for (uint32_t i = 0; i < length; ++i) {
286         napi_value jsFile = nullptr;
287         napi_handle_scope scope = nullptr;
288         napi_open_handle_scope(env, &scope);
289         napi_get_element(env, jsFiles, i, &jsFile);
290         if (jsFile == nullptr) {
291             continue;
292         }
293 
294         Upload::File file;
295         bool ret = Convert2FileL5(env, jsFile, file);
296         if (!ret) {
297             continue;
298         }
299         files.push_back(file);
300         napi_close_handle_scope(env, scope);
301     }
302     return files;
303 }
304 
Convert2RequestData(napi_env env,napi_value jsRequestData)305 Upload::RequestData JSUtil::Convert2RequestData(napi_env env, napi_value jsRequestData)
306 {
307     Upload::RequestData requestData;
308     napi_value value = nullptr;
309     napi_get_named_property(env, jsRequestData, "name", &value);
310     if (value != nullptr) {
311         requestData.name = Convert2String(env, value);
312     }
313     value = nullptr;
314     napi_get_named_property(env, jsRequestData, "value", &value);
315     if (value != nullptr) {
316         requestData.value = Convert2String(env, value);
317     }
318     return requestData;
319 }
320 
Convert2RequestDataVector(napi_env env,napi_value jsRequestDatas)321 std::vector<Upload::RequestData> JSUtil::Convert2RequestDataVector(napi_env env, napi_value jsRequestDatas)
322 {
323     bool isArray = false;
324     napi_is_array(env, jsRequestDatas, &isArray);
325     NAPI_ASSERT_BASE(env, isArray, "not array", {});
326     uint32_t length = 0;
327     napi_get_array_length(env, jsRequestDatas, &length);
328     std::vector<Upload::RequestData> requestDatas;
329     for (uint32_t i = 0; i < length; ++i) {
330         napi_value requestData = nullptr;
331         napi_get_element(env, jsRequestDatas, i, &requestData);
332         if (requestData == nullptr) {
333             continue;
334         }
335         requestDatas.push_back(Convert2RequestData(env, requestData));
336     }
337     return requestDatas;
338 }
339 
CreateBusinessError(napi_env env,const ExceptionErrorCode & errorCode,const std::string & errorMessage)340 napi_value JSUtil::CreateBusinessError(
341     napi_env env, const ExceptionErrorCode &errorCode, const std::string &errorMessage)
342 {
343     napi_value error = nullptr;
344     napi_value code = nullptr;
345     napi_value msg = nullptr;
346     auto iter = ErrorCodeToMsg.find(errorCode);
347     std::string strMsg = (iter != ErrorCodeToMsg.end() ? iter->second : "") + "   " + errorMessage;
348     NAPI_CALL(env, napi_create_string_utf8(env, strMsg.c_str(), strMsg.length(), &msg));
349     NAPI_CALL(env, napi_create_uint32(env, errorCode, &code));
350     NAPI_CALL(env, napi_create_error(env, nullptr, msg, &error));
351     napi_set_named_property(env, error, "code", code);
352     return error;
353 }
354 
355 } // namespace OHOS::Request::Upload