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