• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-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 "request_context.h"
17 
18 #include <algorithm>
19 
20 #include "constant.h"
21 #include "http_exec.h"
22 #include "napi_utils.h"
23 #include "netstack_common_utils.h"
24 #include "netstack_log.h"
25 
26 static constexpr const int PARAM_JUST_URL = 1;
27 
28 static constexpr const int PARAM_JUST_URL_OR_CALLBACK = 1;
29 
30 static constexpr const int PARAM_URL_AND_OPTIONS_OR_CALLBACK = 2;
31 
32 static constexpr const int PARAM_URL_AND_OPTIONS_AND_CALLBACK = 3;
33 
34 namespace OHOS::NetStack {
35 std::map<RequestContext *, napi_env> RequestContext::envMap_;
36 std::mutex RequestContext::envMutex_;
37 static const std::map<int32_t, const char *> HTTP_ERR_MAP = {
38     {HTTP_UNSUPPORTED_PROTOCOL, "Unsupported protocol"},
39     {HTTP_URL_MALFORMAT, "URL using bad/illegal format or missing URL"},
40     {HTTP_COULDNT_RESOLVE_PROXY, "Couldn't resolve proxy name"},
41     {HTTP_COULDNT_RESOLVE_HOST, "Couldn't resolve host name"},
42     {HTTP_COULDNT_CONNECT, "Couldn't connect to server"},
43     {HTTP_WEIRD_SERVER_REPLY, "Weird server reply"},
44     {HTTP_REMOTE_ACCESS_DENIED, "Access denied to remote resource"},
45     {HTTP_HTTP2_ERROR, "Error in the HTTP2 framing layer"},
46     {HTTP_PARTIAL_FILE, "Transferred a partial file"},
47     {HTTP_WRITE_ERROR, "Failed writing received data to disk/application"},
48     {HTTP_UPLOAD_FAILED, "Upload failed"},
49     {HTTP_READ_ERROR, "Failed to open/read local data from file/application"},
50     {HTTP_OUT_OF_MEMORY, "Out of memory"},
51     {HTTP_OPERATION_TIMEDOUT, "Timeout was reached"},
52     {HTTP_TOO_MANY_REDIRECTS, "Number of redirects hit maximum amount"},
53     {HTTP_GOT_NOTHING, "Server returned nothing (no headers, no data)"},
54     {HTTP_SEND_ERROR, "Failed sending data to the peer"},
55     {HTTP_RECV_ERROR, "Failure when receiving data from the peer"},
56     {HTTP_SSL_CERTPROBLEM, "Problem with the local SSL certificate"},
57     {HTTP_SSL_CIPHER, "Couldn't use specified SSL cipher"},
58     {HTTP_PEER_FAILED_VERIFICATION, "SSL peer certificate or SSH remote key was not OK"},
59     {HTTP_BAD_CONTENT_ENCODING, "Unrecognized or bad HTTP Content or Transfer-Encoding"},
60     {HTTP_FILESIZE_EXCEEDED, "Maximum file size exceeded"},
61     {HTTP_REMOTE_DISK_FULL, "Disk full or allocation exceeded"},
62     {HTTP_REMOTE_FILE_EXISTS, "Remote file already exists"},
63     {HTTP_SSL_CACERT_BADFILE, "Problem with the SSL CA cert (path? access rights?)"},
64     {HTTP_REMOTE_FILE_NOT_FOUND, "Remote file not found"},
65     {HTTP_AUTH_ERROR, "An authentication function returned an error"},
66     {HTTP_UNKNOWN_OTHER_ERROR, "Unknown Other Error"},
67 };
RequestContext(napi_env env,EventManager * manager)68 RequestContext::RequestContext(napi_env env, EventManager *manager)
69     : BaseContext(env, manager), usingCache_(true), request2_(false), curlHeaderList_(nullptr)
70 {
71     std::lock_guard guard(envMutex_);
72     envMap_[this] = env;
73 }
74 
GetEnv()75 napi_env RequestContext::GetEnv()
76 {
77     std::lock_guard guard(envMutex_);
78     return envMap_[this];
79 }
80 
ParseParams(napi_value * params,size_t paramsCount)81 void RequestContext::ParseParams(napi_value *params, size_t paramsCount)
82 {
83     bool valid = CheckParamsType(params, paramsCount);
84     if (!valid) {
85         if (paramsCount == PARAM_JUST_URL_OR_CALLBACK) {
86             if (NapiUtils::GetValueType(GetEnv(), params[0]) == napi_function) {
87                 SetCallback(params[0]);
88             }
89             return;
90         }
91         if (paramsCount == PARAM_URL_AND_OPTIONS_OR_CALLBACK) {
92             if (NapiUtils::GetValueType(GetEnv(), params[1]) == napi_function) {
93                 SetCallback(params[1]);
94             }
95             return;
96         }
97         if (paramsCount == PARAM_URL_AND_OPTIONS_AND_CALLBACK) {
98             if (NapiUtils::GetValueType(GetEnv(), params[PARAM_URL_AND_OPTIONS_AND_CALLBACK - 1]) == napi_function) {
99                 SetCallback(params[PARAM_URL_AND_OPTIONS_AND_CALLBACK - 1]);
100             }
101             return;
102         }
103         return;
104     }
105 
106     if (paramsCount == PARAM_JUST_URL) {
107         options.SetUrl(NapiUtils::GetStringFromValueUtf8(GetEnv(), params[0]));
108         SetParseOK(true);
109         return;
110     }
111 
112     if (paramsCount == PARAM_URL_AND_OPTIONS_OR_CALLBACK) {
113         napi_valuetype type = NapiUtils::GetValueType(GetEnv(), params[1]);
114         if (type == napi_function) {
115             options.SetUrl(NapiUtils::GetStringFromValueUtf8(GetEnv(), params[0]));
116             SetParseOK(SetCallback(params[1]) == napi_ok);
117             return;
118         }
119         if (type == napi_object) {
120             UrlAndOptions(params[0], params[1]);
121             return;
122         }
123         return;
124     }
125 
126     if (paramsCount == PARAM_URL_AND_OPTIONS_AND_CALLBACK) {
127         if (SetCallback(params[PARAM_URL_AND_OPTIONS_AND_CALLBACK - 1]) != napi_ok) {
128             return;
129         }
130         UrlAndOptions(params[0], params[1]);
131     }
132 }
133 
CheckParamsType(napi_value * params,size_t paramsCount)134 bool RequestContext::CheckParamsType(napi_value *params, size_t paramsCount)
135 {
136     if (paramsCount == PARAM_JUST_URL) {
137         // just url
138         return NapiUtils::GetValueType(GetEnv(), params[0]) == napi_string;
139     }
140     if (paramsCount == PARAM_URL_AND_OPTIONS_OR_CALLBACK) {
141         // should be url, callback or url, options
142         napi_valuetype type = NapiUtils::GetValueType(GetEnv(), params[1]);
143         return NapiUtils::GetValueType(GetEnv(), params[0]) == napi_string &&
144                (type == napi_function || type == napi_object);
145     }
146     if (paramsCount == PARAM_URL_AND_OPTIONS_AND_CALLBACK) {
147         // should be url options and callback
148         return NapiUtils::GetValueType(GetEnv(), params[0]) == napi_string &&
149                NapiUtils::GetValueType(GetEnv(), params[1]) == napi_object &&
150                NapiUtils::GetValueType(GetEnv(), params[PARAM_URL_AND_OPTIONS_AND_CALLBACK - 1]) == napi_function;
151     }
152     return false;
153 }
154 
ParseNumberOptions(napi_value optionsValue)155 void RequestContext::ParseNumberOptions(napi_value optionsValue)
156 {
157     options.SetReadTimeout(NapiUtils::GetUint32Property(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_READ_TIMEOUT));
158     if (options.GetReadTimeout() == 0) {
159         options.SetReadTimeout(HttpConstant::DEFAULT_READ_TIMEOUT);
160     }
161 
162     options.SetConnectTimeout(
163         NapiUtils::GetUint32Property(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_CONNECT_TIMEOUT));
164     if (options.GetConnectTimeout() == 0) {
165         options.SetConnectTimeout(HttpConstant::DEFAULT_CONNECT_TIMEOUT);
166     }
167 
168     if (NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_USING_CACHE)) {
169         napi_value value = NapiUtils::GetNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_USING_CACHE);
170         if (NapiUtils::GetValueType(GetEnv(), value) == napi_boolean) {
171             usingCache_ = NapiUtils::GetBooleanFromValue(GetEnv(), value);
172         }
173     }
174 
175     if (NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_USING_PROTOCOL)) {
176         napi_value value = NapiUtils::GetNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_USING_PROTOCOL);
177         if (NapiUtils::GetValueType(GetEnv(), value) == napi_number) {
178             uint32_t number = NapiUtils::GetUint32FromValue(GetEnv(), value);
179             if (number == static_cast<uint32_t>(HttpProtocol::HTTP1_1) ||
180                 number == static_cast<uint32_t>(HttpProtocol::HTTP2)) {
181                 options.SetUsingProtocol(static_cast<HttpProtocol>(number));
182             }
183         }
184     }
185     if (NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_EXPECT_DATA_TYPE)) {
186         napi_value value =
187             NapiUtils::GetNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_EXPECT_DATA_TYPE);
188         if (NapiUtils::GetValueType(GetEnv(), value) == napi_number) {
189             uint32_t type = NapiUtils::GetUint32FromValue(GetEnv(), value);
190             options.SetHttpDataType(static_cast<HttpDataType>(type));
191         }
192     }
193 
194     if (NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_PRIORITY)) {
195         napi_value value = NapiUtils::GetNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_PRIORITY);
196         if (NapiUtils::GetValueType(GetEnv(), value) == napi_number) {
197             uint32_t priority = NapiUtils::GetUint32FromValue(GetEnv(), value);
198             options.SetPriority(priority);
199         }
200     }
201 }
202 
ParseHeader(napi_value optionsValue)203 void RequestContext::ParseHeader(napi_value optionsValue)
204 {
205     if (!NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_HEADER)) {
206         return;
207     }
208     napi_value header = NapiUtils::GetNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_HEADER);
209     if (NapiUtils::GetValueType(GetEnv(), header) != napi_object) {
210         return;
211     }
212     auto names = NapiUtils::GetPropertyNames(GetEnv(), header);
213     std::for_each(names.begin(), names.end(), [header, this](const std::string &name) {
214         auto value = NapiUtils::GetStringPropertyUtf8(GetEnv(), header, name);
215         if (!value.empty()) {
216             options.SetHeader(CommonUtils::ToLower(name), value);
217         }
218     });
219 }
220 
ParseExtraData(napi_value optionsValue)221 bool RequestContext::ParseExtraData(napi_value optionsValue)
222 {
223     if (!NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_EXTRA_DATA)) {
224         NETSTACK_LOGI("no extraData");
225         return true;
226     }
227     napi_value extraData = NapiUtils::GetNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_EXTRA_DATA);
228 
229     if (HttpExec::MethodForGet(options.GetMethod())) {
230         std::string url = options.GetUrl();
231         std::string param;
232         auto index = url.find(HttpConstant::HTTP_URL_PARAM_START);
233         if (index != std::string::npos) {
234             param = url.substr(index + 1);
235             url.resize(index);
236         }
237 
238         napi_valuetype type = NapiUtils::GetValueType(GetEnv(), extraData);
239         if (type == napi_string) {
240             std::string extraParam = NapiUtils::GetStringFromValueUtf8(GetEnv(), extraData);
241 
242             options.SetUrl(HttpExec::MakeUrl(url, param, extraParam));
243             return true;
244         }
245         if (type != napi_object) {
246             return true;
247         }
248 
249         std::string extraParam;
250         auto names = NapiUtils::GetPropertyNames(GetEnv(), extraData);
251         std::for_each(names.begin(), names.end(), [this, extraData, &extraParam](std::string name) {
252             auto value = NapiUtils::GetStringPropertyUtf8(GetEnv(), extraData, name);
253             NETSTACK_LOGI("url param name = ..., value = ...");
254             if (!name.empty() && !value.empty()) {
255                 bool encodeName = HttpExec::EncodeUrlParam(name);
256                 bool encodeValue = HttpExec::EncodeUrlParam(value);
257                 if (encodeName || encodeValue) {
258                     options.SetHeader(CommonUtils::ToLower(HttpConstant::HTTP_CONTENT_TYPE),
259                                       HttpConstant::HTTP_CONTENT_TYPE_URL_ENCODE);
260                 }
261                 extraParam +=
262                     name + HttpConstant::HTTP_URL_NAME_VALUE_SEPARATOR + value + HttpConstant::HTTP_URL_PARAM_SEPARATOR;
263             }
264         });
265         if (!extraParam.empty()) {
266             extraParam.pop_back(); // remove the last &
267         }
268 
269         options.SetUrl(HttpExec::MakeUrl(url, param, extraParam));
270         return true;
271     }
272 
273     if (HttpExec::MethodForPost(options.GetMethod())) {
274         return GetRequestBody(extraData);
275     }
276     return false;
277 }
278 
ParseUsingHttpProxy(napi_value optionsValue)279 void RequestContext::ParseUsingHttpProxy(napi_value optionsValue)
280 {
281     if (!NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_USING_HTTP_PROXY)) {
282         NETSTACK_LOGI("Do not use http proxy");
283         return;
284     }
285     napi_value httpProxyValue =
286         NapiUtils::GetNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_USING_HTTP_PROXY);
287     napi_valuetype type = NapiUtils::GetValueType(GetEnv(), httpProxyValue);
288     if (type == napi_boolean) {
289         bool usingProxy = NapiUtils::GetBooleanFromValue(GetEnv(), httpProxyValue);
290         UsingHttpProxyType usingType = usingProxy ? UsingHttpProxyType::USE_DEFAULT : UsingHttpProxyType::NOT_USE;
291         options.SetUsingHttpProxyType(usingType);
292         return;
293     }
294     if (type != napi_object) {
295         return;
296     }
297     std::string host = NapiUtils::GetStringPropertyUtf8(GetEnv(), httpProxyValue, HttpConstant::HTTP_PROXY_KEY_HOST);
298     int32_t port = NapiUtils::GetInt32Property(GetEnv(), httpProxyValue, HttpConstant::HTTP_PROXY_KEY_PORT);
299     std::string exclusionList;
300     if (NapiUtils::HasNamedProperty(GetEnv(), httpProxyValue, HttpConstant::HTTP_PROXY_KEY_EXCLUSION_LIST)) {
301         napi_value exclusionListValue =
302             NapiUtils::GetNamedProperty(GetEnv(), httpProxyValue, HttpConstant::HTTP_PROXY_KEY_EXCLUSION_LIST);
303         uint32_t listLength = NapiUtils::GetArrayLength(GetEnv(), exclusionListValue);
304         for (uint32_t index = 0; index < listLength; ++index) {
305             napi_value exclusionValue = NapiUtils::GetArrayElement(GetEnv(), exclusionListValue, index);
306             std::string exclusion = NapiUtils::GetStringFromValueUtf8(GetEnv(), exclusionValue);
307             if (index != 0) {
308                 exclusionList = exclusionList + HttpConstant::HTTP_PROXY_EXCLUSIONS_SEPARATOR;
309             }
310             exclusionList += exclusion;
311         }
312     }
313     options.SetSpecifiedHttpProxy(host, port, exclusionList);
314     options.SetUsingHttpProxyType(UsingHttpProxyType::USE_SPECIFIED);
315 }
316 
GetRequestBody(napi_value extraData)317 bool RequestContext::GetRequestBody(napi_value extraData)
318 {
319     /* if body is empty, return false, or curl will wait for body */
320 
321     napi_valuetype type = NapiUtils::GetValueType(GetEnv(), extraData);
322     if (type == napi_string) {
323         auto body = NapiUtils::GetStringFromValueUtf8(GetEnv(), extraData);
324         if (body.empty()) {
325             return false;
326         }
327         options.SetBody(body.c_str(), body.size());
328         return true;
329     }
330 
331     if (NapiUtils::ValueIsArrayBuffer(GetEnv(), extraData)) {
332         size_t length = 0;
333         void *data = NapiUtils::GetInfoFromArrayBufferValue(GetEnv(), extraData, &length);
334         if (data == nullptr) {
335             return false;
336         }
337         options.SetBody(data, length);
338         return true;
339     }
340 
341     if (type == napi_object) {
342         std::string body = NapiUtils::GetStringFromValueUtf8(GetEnv(), NapiUtils::JsonStringify(GetEnv(), extraData));
343         if (body.empty()) {
344             return false;
345         }
346         options.SetBody(body.c_str(), body.length());
347         return true;
348     }
349 
350     NETSTACK_LOGE("only support string arraybuffer and object");
351     return false;
352 }
353 
UrlAndOptions(napi_value urlValue,napi_value optionsValue)354 void RequestContext::UrlAndOptions(napi_value urlValue, napi_value optionsValue)
355 {
356     options.SetUrl(NapiUtils::GetStringFromValueUtf8(GetEnv(), urlValue));
357 
358     std::string method = NapiUtils::GetStringPropertyUtf8(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_METHOD);
359     if (method.empty()) {
360         method = HttpConstant::HTTP_METHOD_GET;
361     }
362     options.SetMethod(method);
363 
364     ParseNumberOptions(optionsValue);
365     ParseUsingHttpProxy(optionsValue);
366 
367     /* parse extra data here to recover header */
368     if (!ParseExtraData(optionsValue)) {
369         return;
370     }
371 
372     ParseHeader(optionsValue);
373     SetParseOK(true);
374 }
375 
IsUsingCache() const376 bool RequestContext::IsUsingCache() const
377 {
378     return usingCache_;
379 }
380 
SetCurlHeaderList(struct curl_slist * curlHeaderList)381 void RequestContext::SetCurlHeaderList(struct curl_slist *curlHeaderList)
382 {
383     curlHeaderList_ = curlHeaderList;
384 }
385 
GetCurlHeaderList()386 struct curl_slist *RequestContext::GetCurlHeaderList()
387 {
388     return curlHeaderList_;
389 }
~RequestContext()390 RequestContext::~RequestContext()
391 {
392     if (curlHeaderList_ != nullptr) {
393         curl_slist_free_all(curlHeaderList_);
394     }
395     std::lock_guard guard(envMutex_);
396     auto it = envMap_.find(this);
397     if (it != envMap_.end()) {
398         envMap_.erase(it);
399     }
400     NETSTACK_LOGI("RequestContext is destructed by the destructor");
401 }
402 
SetCacheResponse(const HttpResponse & cacheResponse)403 void RequestContext::SetCacheResponse(const HttpResponse &cacheResponse)
404 {
405     cacheResponse_ = cacheResponse;
406 }
SetResponseByCache()407 void RequestContext::SetResponseByCache()
408 {
409     response = cacheResponse_;
410 }
411 
GetErrorCode() const412 int32_t RequestContext::GetErrorCode() const
413 {
414     auto err = BaseContext::GetErrorCode();
415     if (err == PARSE_ERROR_CODE) {
416         return PARSE_ERROR_CODE;
417     }
418 
419     if (BaseContext::IsPermissionDenied()) {
420         return PERMISSION_DENIED_CODE;
421     }
422 
423     if (HTTP_ERR_MAP.find(err + HTTP_ERROR_CODE_BASE) != HTTP_ERR_MAP.end()) {
424         return err + HTTP_ERROR_CODE_BASE;
425     }
426     return HTTP_UNKNOWN_OTHER_ERROR;
427 }
428 
GetErrorMessage() const429 std::string RequestContext::GetErrorMessage() const
430 {
431     auto err = BaseContext::GetErrorCode();
432     if (err == PARSE_ERROR_CODE) {
433         return PARSE_ERROR_MSG;
434     }
435 
436     if (BaseContext::IsPermissionDenied()) {
437         return PERMISSION_DENIED_MSG;
438     }
439 
440     return HTTP_ERR_MAP.find(err + HTTP_ERROR_CODE_BASE)->second;
441 }
442 
EnableRequest2()443 void RequestContext::EnableRequest2()
444 {
445     request2_ = true;
446 }
447 
IsRequest2()448 bool RequestContext::IsRequest2()
449 {
450     return request2_;
451 }
452 
SetTotalLen(curl_off_t totalLen)453 void RequestContext::SetTotalLen(curl_off_t totalLen)
454 {
455     std::lock_guard<std::mutex> lock(totalLenLock_);
456     totalLen_.push(totalLen);
457 }
458 
GetTotalLen()459 curl_off_t RequestContext::GetTotalLen()
460 {
461     std::lock_guard<std::mutex> lock(totalLenLock_);
462     if (!totalLen_.empty()) {
463         return totalLen_.front();
464     }
465     return 0;
466 }
467 
PopTotalLen()468 void RequestContext::PopTotalLen()
469 {
470     std::lock_guard<std::mutex> lock(totalLenLock_);
471     if (!totalLen_.empty()) {
472         totalLen_.pop();
473     }
474 }
475 
SetNowLen(curl_off_t nowLen)476 void RequestContext::SetNowLen(curl_off_t nowLen)
477 {
478     std::lock_guard<std::mutex> lock(nowLenLock_);
479     nowLen_.push(nowLen);
480 }
481 
GetNowLen()482 curl_off_t RequestContext::GetNowLen()
483 {
484     std::lock_guard<std::mutex> lock(nowLenLock_);
485     if (!nowLen_.empty()) {
486         return nowLen_.front();
487     }
488     return 0;
489 }
490 
PopNowLen()491 void RequestContext::PopNowLen()
492 {
493     std::lock_guard<std::mutex> lock(nowLenLock_);
494     if (!nowLen_.empty()) {
495         nowLen_.pop();
496     }
497 }
498 
SetTempData(const void * data,size_t size)499 void RequestContext::SetTempData(const void *data, size_t size)
500 {
501     std::lock_guard<std::mutex> lock(tempDataLock_);
502     std::string tempString;
503     tempString.append(reinterpret_cast<const char *>(data), size);
504     tempData_.push(tempString);
505 }
506 
GetTempData()507 std::string RequestContext::GetTempData()
508 {
509     std::lock_guard<std::mutex> lock(tempDataLock_);
510     if (!tempData_.empty()) {
511         return tempData_.front();
512     }
513     return {};
514 }
515 
PopTempData()516 void RequestContext::PopTempData()
517 {
518     std::lock_guard<std::mutex> lock(tempDataLock_);
519     if (!tempData_.empty()) {
520         tempData_.pop();
521     }
522 }
523 } // namespace OHOS::NetStack
524