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 "http_exec.h"
17
18 #include <cstddef>
19 #include <cstring>
20 #include <memory>
21 #include <thread>
22 #include <unistd.h>
23
24 #ifdef HTTP_PROXY_ENABLE
25 #include "parameter.h"
26 #endif
27 #ifdef HAS_NETMANAGER_BASE
28 #include "http_proxy.h"
29 #include "net_conn_client.h"
30 #endif
31 #include "base64_utils.h"
32 #include "cache_proxy.h"
33 #include "constant.h"
34 #include "event_list.h"
35 #include "http_async_work.h"
36 #include "http_time.h"
37 #include "napi_utils.h"
38 #include "netstack_common_utils.h"
39 #include "netstack_log.h"
40 #include "securec.h"
41
42 #define NETSTACK_CURL_EASY_SET_OPTION(handle, opt, data, asyncContext) \
43 do { \
44 CURLcode result = curl_easy_setopt(handle, opt, data); \
45 if (result != CURLE_OK) { \
46 const char *err = curl_easy_strerror(result); \
47 NETSTACK_LOGE("Failed to set option: %{public}s, %{public}s %{public}d", #opt, err, result); \
48 (asyncContext)->SetErrorCode(result); \
49 return false; \
50 } \
51 } while (0)
52
53 namespace OHOS::NetStack::Http {
54 static constexpr size_t MAX_LIMIT = 5 * 1024 * 1024;
55 static constexpr int CURL_TIMEOUT_MS = 50;
56 static constexpr int CONDITION_TIMEOUT_S = 3600;
57 static constexpr int CURL_MAX_WAIT_MSECS = 10;
58 static constexpr int CURL_HANDLE_NUM = 10;
59 #ifdef HTTP_PROXY_ENABLE
60 static constexpr int32_t SYSPARA_MAX_SIZE = 128;
61 static constexpr const char *DEFAULT_HTTP_PROXY_HOST = "NONE";
62 static constexpr const char *DEFAULT_HTTP_PROXY_PORT = "0";
63 static constexpr const char *DEFAULT_HTTP_PROXY_EXCLUSION_LIST = "NONE";
64 static constexpr const char *HTTP_PROXY_HOST_KEY = "persist.netmanager_base.http_proxy.host";
65 static constexpr const char *HTTP_PROXY_PORT_KEY = "persist.netmanager_base.http_proxy.port";
66 static constexpr const char *HTTP_PROXY_EXCLUSIONS_KEY = "persist.netmanager_base.http_proxy.exclusion_list";
67 #endif
68
CallbackTemplate(uv_work_t * work,int status)69 template <napi_value (*MakeJsValue)(napi_env, void *)> static void CallbackTemplate(uv_work_t *work, int status)
70 {
71 (void)status;
72
73 auto workWrapper = static_cast<UvWorkWrapper *>(work->data);
74 napi_env env = workWrapper->env;
75 auto closeScope = [env](napi_handle_scope scope) { NapiUtils::CloseScope(env, scope); };
76 std::unique_ptr<napi_handle_scope__, decltype(closeScope)> scope(NapiUtils::OpenScope(env), closeScope);
77
78 napi_value obj = MakeJsValue(env, workWrapper->data);
79
80 std::pair<napi_value, napi_value> arg = {NapiUtils::GetUndefined(workWrapper->env), obj};
81 workWrapper->manager->Emit(workWrapper->type, arg);
82
83 delete workWrapper;
84 delete work;
85 }
86
AddCurlHandle(CURL * handle,RequestContext * context)87 bool HttpExec::AddCurlHandle(CURL *handle, RequestContext *context)
88 {
89 if (handle == nullptr || staticVariable_.curlMulti == nullptr) {
90 NETSTACK_LOGE("handle nullptr");
91 return false;
92 }
93
94 std::thread emplaceInfoThread([context, handle] {
95 std::lock_guard guard(staticVariable_.curlMultiMutex);
96 staticVariable_.infoQueue.emplace(context, handle);
97 staticVariable_.conditionVariable.notify_all();
98 });
99 emplaceInfoThread.detach();
100
101 return true;
102 }
103
104 HttpExec::StaticVariable HttpExec::staticVariable_; /* NOLINT */
105
RequestWithoutCache(RequestContext * context)106 bool HttpExec::RequestWithoutCache(RequestContext *context)
107 {
108 if (!staticVariable_.initialized) {
109 NETSTACK_LOGE("curl not init");
110 return false;
111 }
112
113 auto handle = curl_easy_init();
114 if (!handle) {
115 NETSTACK_LOGE("Failed to create fetch task");
116 return false;
117 }
118
119 std::vector<std::string> vec;
120 std::for_each(context->options.GetHeader().begin(), context->options.GetHeader().end(),
121 [&vec](const std::pair<std::string, std::string> &p) {
122 vec.emplace_back(p.first + HttpConstant::HTTP_HEADER_SEPARATOR + p.second);
123 });
124 context->SetCurlHeaderList(MakeHeaders(vec));
125
126 if (!SetOption(handle, context, context->GetCurlHeaderList())) {
127 NETSTACK_LOGE("set option failed");
128 return false;
129 }
130
131 context->response.SetRequestTime(HttpTime::GetNowTimeGMT());
132
133 if (!AddCurlHandle(handle, context)) {
134 NETSTACK_LOGE("add handle failed");
135 return false;
136 }
137
138 return true;
139 }
140
GetCurlDataFromHandle(CURL * handle,RequestContext * context,CURLMSG curlMsg,CURLcode result)141 bool HttpExec::GetCurlDataFromHandle(CURL *handle, RequestContext *context, CURLMSG curlMsg, CURLcode result)
142 {
143 if (curlMsg != CURLMSG_DONE) {
144 NETSTACK_LOGE("CURLMSG %{public}s", std::to_string(curlMsg).c_str());
145 context->SetErrorCode(NapiUtils::NETSTACK_NAPI_INTERNAL_ERROR);
146 return false;
147 }
148
149 if (result != CURLE_OK) {
150 context->SetErrorCode(result);
151 NETSTACK_LOGE("CURLcode result %{public}s", std::to_string(result).c_str());
152 return false;
153 }
154
155 context->response.SetResponseTime(HttpTime::GetNowTimeGMT());
156
157 int64_t responseCode;
158 CURLcode code = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
159 if (code != CURLE_OK) {
160 context->SetErrorCode(code);
161 return false;
162 }
163 context->response.SetResponseCode(responseCode);
164 if (context->response.GetResponseCode() == static_cast<uint32_t>(ResponseCode::NOT_MODIFIED)) {
165 NETSTACK_LOGI("cache is NOT_MODIFIED, we use the cache");
166 context->SetResponseByCache();
167 return true;
168 }
169 NETSTACK_LOGD("responseCode is %{public}s", std::to_string(responseCode).c_str());
170
171 struct curl_slist *cookies = nullptr;
172 code = curl_easy_getinfo(handle, CURLINFO_COOKIELIST, &cookies);
173 if (code != CURLE_OK) {
174 context->SetErrorCode(code);
175 return false;
176 }
177
178 std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)> cookiesHandle(cookies, curl_slist_free_all);
179 while (cookies) {
180 context->response.AppendCookies(cookies->data, strlen(cookies->data));
181 if (cookies->next != nullptr) {
182 context->response.AppendCookies(HttpConstant::HTTP_LINE_SEPARATOR,
183 strlen(HttpConstant::HTTP_LINE_SEPARATOR));
184 }
185 cookies = cookies->next;
186 }
187 return true;
188 }
189
190 #ifdef ENABLE_EVENT_HANDLER
HttpEventHandlerCallback(RequestContext * context)191 void HttpExec::HttpEventHandlerCallback(RequestContext *context)
192 {
193 std::mutex lock;
194 if (EventManager::IsManagerValid(context->GetManager())) {
195 if (context->IsRequestInStream()) {
196 auto manager = context->GetManager();
197 auto eventHandler = manager->GetNetstackEventHandler();
198 if (!eventHandler) {
199 NETSTACK_LOGE("netstack eventHandler is nullptr");
200 return;
201 }
202 eventHandler->PostSyncTask([&context, &lock]() {
203 std::lock_guard<std::mutex> callbackLock(lock);
204 NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context,
205 HttpAsyncWork::RequestInStreamCallback);
206 });
207 if (context->IsExecOK()) {
208 eventHandler->PostSyncTask([&context, &lock]() {
209 std::lock_guard<std::mutex> callbackLock(lock);
210 NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context, OnDataEnd);
211 });
212 }
213 } else {
214 NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context, HttpAsyncWork::RequestCallback);
215 }
216 }
217 }
218 #endif
219
HandleCurlData(CURLMsg * msg)220 void HttpExec::HandleCurlData(CURLMsg *msg)
221 {
222 if (msg == nullptr) {
223 return;
224 }
225
226 auto handle = msg->easy_handle;
227 if (handle == nullptr) {
228 return;
229 }
230
231 auto it = staticVariable_.contextMap.find(handle);
232 if (it == staticVariable_.contextMap.end()) {
233 NETSTACK_LOGE("can not find context");
234 return;
235 }
236
237 auto context = it->second;
238 staticVariable_.contextMap.erase(it);
239 if (context == nullptr) {
240 NETSTACK_LOGE("can not find context");
241 return;
242 }
243
244 NETSTACK_LOGI("priority = %{public}d", context->options.GetPriority());
245 context->SetExecOK(GetCurlDataFromHandle(handle, context, msg->msg, msg->data.result));
246 if (context->IsExecOK()) {
247 CacheProxy proxy(context->options);
248 proxy.WriteResponseToCache(context->response);
249 }
250
251 if (context->GetManager() == nullptr) {
252 NETSTACK_LOGE("can not find context manager");
253 return;
254 }
255 #ifdef ENABLE_EVENT_HANDLER
256 HttpEventHandlerCallback(context);
257 #endif
258 }
259
ExecRequest(RequestContext * context)260 bool HttpExec::ExecRequest(RequestContext *context)
261 {
262 if (!CommonUtils::HasInternetPermission()) {
263 context->SetPermissionDenied(true);
264 return false;
265 }
266 if (context->GetManager()->IsEventDestroy()) {
267 return false;
268 }
269 context->options.SetRequestTime(HttpTime::GetNowTimeGMT());
270 CacheProxy proxy(context->options);
271 if (context->IsUsingCache() && proxy.ReadResponseFromCache(context)) {
272 return true;
273 }
274
275 if (!RequestWithoutCache(context)) {
276 context->SetErrorCode(NapiUtils::NETSTACK_NAPI_INTERNAL_ERROR);
277 if (EventManager::IsManagerValid(context->GetManager())) {
278 if (context->IsRequestInStream()) {
279 NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context,
280 HttpAsyncWork::RequestInStreamCallback);
281 } else {
282 NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context, HttpAsyncWork::RequestCallback);
283 }
284 }
285 return false;
286 }
287
288 return true;
289 }
290
RequestCallback(RequestContext * context)291 napi_value HttpExec::RequestCallback(RequestContext *context)
292 {
293 napi_value object = NapiUtils::CreateObject(context->GetEnv());
294 if (NapiUtils::GetValueType(context->GetEnv(), object) != napi_object) {
295 return nullptr;
296 }
297
298 NapiUtils::SetUint32Property(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESPONSE_CODE,
299 context->response.GetResponseCode());
300 NapiUtils::SetStringPropertyUtf8(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_COOKIES,
301 context->response.GetCookies());
302
303 napi_value header = MakeResponseHeader(context->GetEnv(), context);
304 if (NapiUtils::GetValueType(context->GetEnv(), header) == napi_object) {
305 NapiUtils::SetNamedProperty(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_HEADER, header);
306 }
307
308 if (context->options.GetHttpDataType() != HttpDataType::NO_DATA_TYPE && ProcByExpectDataType(object, context)) {
309 return object;
310 }
311
312 auto contentType = CommonUtils::ToLower(const_cast<std::map<std::string, std::string> &>(
313 context->response.GetHeader())[HttpConstant::HTTP_CONTENT_TYPE]);
314 if (contentType.find(HttpConstant::HTTP_CONTENT_TYPE_OCTET_STREAM) != std::string::npos ||
315 contentType.find(HttpConstant::HTTP_CONTENT_TYPE_IMAGE) != std::string::npos) {
316 void *data = nullptr;
317 auto body = context->response.GetResult();
318 napi_value arrayBuffer = NapiUtils::CreateArrayBuffer(context->GetEnv(), body.size(), &data);
319 if (data != nullptr && arrayBuffer != nullptr) {
320 if (memcpy_s(data, body.size(), body.c_str(), body.size()) != EOK) {
321 NETSTACK_LOGE("memcpy_s failed!");
322 return object;
323 }
324 NapiUtils::SetNamedProperty(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT, arrayBuffer);
325 }
326 NapiUtils::SetUint32Property(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT_TYPE,
327 static_cast<uint32_t>(HttpDataType::ARRAY_BUFFER));
328 return object;
329 }
330
331 /* now just support utf8 */
332 NapiUtils::SetStringPropertyUtf8(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT,
333 context->response.GetResult());
334 NapiUtils::SetUint32Property(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT_TYPE,
335 static_cast<uint32_t>(HttpDataType::STRING));
336 return object;
337 }
338
RequestInStreamCallback(OHOS::NetStack::Http::RequestContext * context)339 napi_value HttpExec::RequestInStreamCallback(OHOS::NetStack::Http::RequestContext *context)
340 {
341 napi_value number = NapiUtils::CreateUint32(context->GetEnv(), context->response.GetResponseCode());
342 if (NapiUtils::GetValueType(context->GetEnv(), number) != napi_number) {
343 return nullptr;
344 }
345 return number;
346 }
347
MakeUrl(const std::string & url,std::string param,const std::string & extraParam)348 std::string HttpExec::MakeUrl(const std::string &url, std::string param, const std::string &extraParam)
349 {
350 if (param.empty()) {
351 param += extraParam;
352 } else {
353 param += HttpConstant::HTTP_URL_PARAM_SEPARATOR;
354 param += extraParam;
355 }
356
357 if (param.empty()) {
358 return url;
359 }
360
361 return url + HttpConstant::HTTP_URL_PARAM_START + param;
362 }
363
MethodForGet(const std::string & method)364 bool HttpExec::MethodForGet(const std::string &method)
365 {
366 return (method == HttpConstant::HTTP_METHOD_HEAD || method == HttpConstant::HTTP_METHOD_OPTIONS ||
367 method == HttpConstant::HTTP_METHOD_TRACE || method == HttpConstant::HTTP_METHOD_GET ||
368 method == HttpConstant::HTTP_METHOD_CONNECT);
369 }
370
MethodForPost(const std::string & method)371 bool HttpExec::MethodForPost(const std::string &method)
372 {
373 return (method == HttpConstant::HTTP_METHOD_POST || method == HttpConstant::HTTP_METHOD_PUT ||
374 method == HttpConstant::HTTP_METHOD_DELETE);
375 }
376
EncodeUrlParam(std::string & str)377 bool HttpExec::EncodeUrlParam(std::string &str)
378 {
379 char encoded[4];
380 std::string encodeOut;
381 size_t length = strlen(str.c_str());
382 for (size_t i = 0; i < length; ++i) {
383 auto c = static_cast<uint8_t>(str.c_str()[i]);
384 if (IsUnReserved(c)) {
385 encodeOut += static_cast<char>(c);
386 } else {
387 if (sprintf_s(encoded, sizeof(encoded), "%%%02X", c) < 0) {
388 return false;
389 }
390 encodeOut += encoded;
391 }
392 }
393
394 if (str == encodeOut) {
395 return false;
396 }
397 str = encodeOut;
398 return true;
399 }
400
AddRequestInfo()401 void HttpExec::AddRequestInfo()
402 {
403 std::lock_guard guard(staticVariable_.curlMultiMutex);
404 int num = 0;
405 while (!staticVariable_.infoQueue.empty()) {
406 if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
407 break;
408 }
409
410 auto info = staticVariable_.infoQueue.top();
411 staticVariable_.infoQueue.pop();
412 auto ret = curl_multi_add_handle(staticVariable_.curlMulti, info.handle);
413 if (ret == CURLM_OK) {
414 staticVariable_.contextMap[info.handle] = info.context;
415 }
416
417 ++num;
418 if (num >= CURL_HANDLE_NUM) {
419 break;
420 }
421 }
422 }
423
RunThread()424 void HttpExec::RunThread()
425 {
426 while (staticVariable_.runThread && staticVariable_.curlMulti != nullptr) {
427 AddRequestInfo();
428 SendRequest();
429 ReadResponse();
430 std::this_thread::sleep_for(std::chrono::milliseconds(CURL_TIMEOUT_MS));
431 std::unique_lock l(staticVariable_.curlMultiMutex);
432 staticVariable_.conditionVariable.wait_for(l, std::chrono::seconds(CONDITION_TIMEOUT_S), [] {
433 return !staticVariable_.infoQueue.empty() || !staticVariable_.contextMap.empty();
434 });
435 }
436 }
437
SendRequest()438 void HttpExec::SendRequest()
439 {
440 std::lock_guard guard(staticVariable_.curlMultiMutex);
441
442 int runningHandle = 0;
443 int num = 0;
444 do {
445 if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
446 break;
447 }
448
449 auto ret = curl_multi_perform(staticVariable_.curlMulti, &runningHandle);
450
451 if (runningHandle > 0) {
452 ret = curl_multi_poll(staticVariable_.curlMulti, nullptr, 0, CURL_MAX_WAIT_MSECS, nullptr);
453 }
454
455 if (ret != CURLM_OK) {
456 return;
457 }
458
459 ++num;
460 if (num >= CURL_HANDLE_NUM) {
461 break;
462 }
463 } while (runningHandle > 0);
464 }
465
ReadResponse()466 void HttpExec::ReadResponse()
467 {
468 std::lock_guard guard(staticVariable_.curlMultiMutex);
469 CURLMsg *msg = nullptr; /* NOLINT */
470 do {
471 if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
472 break;
473 }
474
475 int leftMsg;
476 msg = curl_multi_info_read(staticVariable_.curlMulti, &leftMsg);
477 if (msg) {
478 if (msg->msg == CURLMSG_DONE) {
479 HandleCurlData(msg);
480 }
481 if (msg->easy_handle) {
482 (void)curl_multi_remove_handle(staticVariable_.curlMulti, msg->easy_handle);
483 (void)curl_easy_cleanup(msg->easy_handle);
484 }
485 }
486 } while (msg);
487 }
488
GetGlobalHttpProxyInfo(std::string & host,int32_t & port,std::string & exclusions)489 void HttpExec::GetGlobalHttpProxyInfo(std::string &host, int32_t &port, std::string &exclusions)
490 {
491 #ifdef HTTP_PROXY_ENABLE
492 char httpProxyHost[SYSPARA_MAX_SIZE] = {0};
493 char httpProxyPort[SYSPARA_MAX_SIZE] = {0};
494 char httpProxyExclusions[SYSPARA_MAX_SIZE] = {0};
495 GetParameter(HTTP_PROXY_HOST_KEY, DEFAULT_HTTP_PROXY_HOST, httpProxyHost, sizeof(httpProxyHost));
496 GetParameter(HTTP_PROXY_PORT_KEY, DEFAULT_HTTP_PROXY_PORT, httpProxyPort, sizeof(httpProxyPort));
497 GetParameter(HTTP_PROXY_EXCLUSIONS_KEY, DEFAULT_HTTP_PROXY_EXCLUSION_LIST, httpProxyExclusions,
498 sizeof(httpProxyExclusions));
499
500 host = Base64::Decode(httpProxyHost);
501 if (host == DEFAULT_HTTP_PROXY_HOST) {
502 host = std::string();
503 }
504 exclusions = httpProxyExclusions;
505 if (exclusions == DEFAULT_HTTP_PROXY_EXCLUSION_LIST) {
506 exclusions = std::string();
507 }
508
509 port = std::atoi(httpProxyPort);
510 #endif
511 }
512
GetHttpProxyInfo(RequestContext * context,std::string & host,int32_t & port,std::string & exclusions)513 void HttpExec::GetHttpProxyInfo(RequestContext *context, std::string &host, int32_t &port, std::string &exclusions)
514 {
515 if (context->options.GetUsingHttpProxyType() == UsingHttpProxyType::USE_DEFAULT) {
516 #ifdef HAS_NETMANAGER_BASE
517 using namespace NetManagerStandard;
518 HttpProxy httpProxy;
519 NetConnClient::GetInstance().GetDefaultHttpProxy(httpProxy);
520 host = httpProxy.GetHost();
521 port = httpProxy.GetPort();
522 exclusions = CommonUtils::ToString(httpProxy.GetExclusionList());
523 #else
524 GetGlobalHttpProxyInfo(host, port, exclusions);
525 #endif
526 } else if (context->options.GetUsingHttpProxyType() == UsingHttpProxyType::USE_SPECIFIED) {
527 context->options.GetSpecifiedHttpProxy(host, port, exclusions);
528 }
529 }
530
Initialize()531 bool HttpExec::Initialize()
532 {
533 std::lock_guard<std::mutex> lock(staticVariable_.mutexForInitialize);
534 if (staticVariable_.initialized) {
535 return true;
536 }
537 NETSTACK_LOGI("call curl_global_init");
538 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
539 NETSTACK_LOGE("Failed to initialize 'curl'");
540 return false;
541 }
542
543 staticVariable_.curlMulti = curl_multi_init();
544 if (staticVariable_.curlMulti == nullptr) {
545 NETSTACK_LOGE("Failed to initialize 'curl_multi'");
546 return false;
547 }
548
549 staticVariable_.workThread = std::thread(RunThread);
550
551 staticVariable_.initialized = true;
552 return staticVariable_.initialized;
553 }
554
SetOtherOption(CURL * curl,OHOS::NetStack::Http::RequestContext * context)555 bool HttpExec::SetOtherOption(CURL *curl, OHOS::NetStack::Http::RequestContext *context)
556 {
557 std::string host, exclusions;
558 int32_t port = 0;
559 GetHttpProxyInfo(context, host, port, exclusions);
560 if (!host.empty()) {
561 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXY, host.c_str(), context);
562 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYPORT, port, context);
563 if (!exclusions.empty()) {
564 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOPROXY, exclusions.c_str(), context);
565 }
566 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP, context);
567 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTPPROXYTUNNEL, 1L, context);
568 }
569
570 #ifdef NETSTACK_PROXY_PASS
571 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYUSERPWD, NETSTACK_PROXY_PASS, context);
572 #endif // NETSTACK_PROXY_PASS
573
574 #ifdef NO_SSL_CERTIFICATION
575 // in real life, you should buy a ssl certification and rename it to /etc/ssl/cert.pem
576 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 0L, context);
577 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 0L, context);
578 #else
579 #ifndef WINDOWS_PLATFORM
580 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CAINFO, context->options.GetCaPath().c_str(), context);
581 #endif // WINDOWS_PLATFORM
582 #endif // NO_SSL_CERTIFICATION
583
584 #ifdef HTTP_CURL_PRINT_VERBOSE
585 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_VERBOSE, 1L, context);
586 #endif
587
588 #ifndef WINDOWS_PLATFORM
589 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_ACCEPT_ENCODING, HttpConstant::HTTP_CONTENT_ENCODING_GZIP, context);
590 #endif
591 return true;
592 }
593
SetOption(CURL * curl,RequestContext * context,struct curl_slist * requestHeader)594 bool HttpExec::SetOption(CURL *curl, RequestContext *context, struct curl_slist *requestHeader)
595 {
596 const std::string &method = context->options.GetMethod();
597 if (!MethodForGet(method) && !MethodForPost(method)) {
598 NETSTACK_LOGE("method %{public}s not supported", method.c_str());
599 return false;
600 }
601
602 if (context->options.GetMethod() == HttpConstant::HTTP_METHOD_HEAD) {
603 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOBODY, 1L, context);
604 }
605
606 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, context->options.GetUrl().c_str(), context);
607 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CUSTOMREQUEST, method.c_str(), context);
608
609 if (MethodForPost(method) && !context->options.GetBody().empty()) {
610 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POST, 1L, context);
611 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDS, context->options.GetBody().c_str(), context);
612 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDSIZE, context->options.GetBody().size(), context);
613 }
614
615 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback, context);
616 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_XFERINFODATA, context, context);
617 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOPROGRESS, 0L, context);
618
619 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEFUNCTION, OnWritingMemoryBody, context);
620 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEDATA, context, context);
621
622 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader, context);
623 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERDATA, context, context);
624
625 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTPHEADER, requestHeader, context);
626
627 // Some servers don't like requests that are made without a user-agent field, so we provide one
628 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_USERAGENT, HttpConstant::HTTP_DEFAULT_USER_AGENT, context);
629
630 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_FOLLOWLOCATION, 1L, context);
631
632 /* first #undef CURL_DISABLE_COOKIES in curl config */
633 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_COOKIEFILE, "", context);
634
635 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOSIGNAL, 1L, context);
636
637 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_TIMEOUT_MS, context->options.GetReadTimeout(), context);
638 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CONNECTTIMEOUT_MS, context->options.GetConnectTimeout(), context);
639
640 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTP_VERSION, context->options.GetHttpVersion(), context);
641 if (!SetOtherOption(curl, context)) {
642 return false;
643 }
644
645 return true;
646 }
647
OnWritingMemoryBody(const void * data,size_t size,size_t memBytes,void * userData)648 size_t HttpExec::OnWritingMemoryBody(const void *data, size_t size, size_t memBytes, void *userData)
649 {
650 auto context = static_cast<RequestContext *>(userData);
651 if (context == nullptr) {
652 return 0;
653 }
654 if (context->GetManager()->IsEventDestroy()) {
655 return 0;
656 }
657 if (context->IsRequestInStream()) {
658 context->SetTempData(data, size * memBytes);
659 NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context, OnDataReceive);
660 return size * memBytes;
661 }
662 if (context->response.GetResult().size() > MAX_LIMIT) {
663 NETSTACK_LOGE("response data exceeds the maximum limit");
664 return 0;
665 }
666 context->response.AppendResult(data, size * memBytes);
667 return size * memBytes;
668 }
669
OnWritingMemoryHeader(const void * data,size_t size,size_t memBytes,void * userData)670 size_t HttpExec::OnWritingMemoryHeader(const void *data, size_t size, size_t memBytes, void *userData)
671 {
672 auto context = static_cast<RequestContext *>(userData);
673 if (context == nullptr) {
674 return 0;
675 }
676 if (context->GetManager()->IsEventDestroy()) {
677 return 0;
678 }
679 if (context->response.GetResult().size() > MAX_LIMIT) {
680 NETSTACK_LOGE("response data exceeds the maximum limit");
681 return 0;
682 }
683 context->response.AppendRawHeader(data, size * memBytes);
684 if (CommonUtils::EndsWith(context->response.GetRawHeader(), HttpConstant::HTTP_RESPONSE_HEADER_SEPARATOR)) {
685 context->response.ParseHeaders();
686 if (context->GetManager() && EventManager::IsManagerValid(context->GetManager())) {
687 context->GetManager()->EmitByUv(ON_HEADER_RECEIVE, context, CallbackTemplate<MakeResponseHeader>);
688 context->GetManager()->EmitByUv(ON_HEADERS_RECEIVE, context, CallbackTemplate<MakeResponseHeader>);
689 }
690 }
691 return size * memBytes;
692 }
693
OnDataReceive(napi_env env,napi_status status,void * data)694 void HttpExec::OnDataReceive(napi_env env, napi_status status, void *data)
695 {
696 auto context = static_cast<RequestContext *>(data);
697 if (context == nullptr) {
698 return;
699 }
700
701 void *buffer = nullptr;
702 auto tempData = context->GetTempData();
703 context->PopTempData();
704 if (tempData.empty()) {
705 NETSTACK_LOGI("[GetTempData] tempDate is empty!");
706 return;
707 }
708 napi_value arrayBuffer = NapiUtils::CreateArrayBuffer(context->GetEnv(), tempData.size(), &buffer);
709 if (buffer == nullptr || arrayBuffer == nullptr) {
710 return;
711 }
712 if (memcpy_s(buffer, tempData.size(), tempData.data(), tempData.size()) != EOK) {
713 NETSTACK_LOGE("[CreateArrayBuffer] memory copy failed");
714 return;
715 }
716 context->Emit(ON_DATA_RECEIVE, std::make_pair(NapiUtils::GetUndefined(context->GetEnv()), arrayBuffer));
717 }
718
OnDataProgress(napi_env env,napi_status status,void * data)719 void HttpExec::OnDataProgress(napi_env env, napi_status status, void *data)
720 {
721 auto context = static_cast<RequestContext *>(data);
722 if (context == nullptr) {
723 return;
724 }
725 auto progress = NapiUtils::CreateObject(context->GetEnv());
726 if (NapiUtils::GetValueType(context->GetEnv(), progress) == napi_undefined) {
727 return;
728 }
729 NapiUtils::SetUint32Property(context->GetEnv(), progress, "receiveSize",
730 static_cast<uint32_t>(context->GetDlLen().nLen));
731 NapiUtils::SetUint32Property(context->GetEnv(), progress, "totalSize",
732 static_cast<uint32_t>(context->GetDlLen().tLen));
733 context->PopDlLen();
734 context->Emit(ON_DATA_RECEIVE_PROGRESS, std::make_pair(NapiUtils::GetUndefined(context->GetEnv()), progress));
735 }
736
OnDataEnd(napi_env env,napi_status status,void * data)737 void HttpExec::OnDataEnd(napi_env env, napi_status status, void *data)
738 {
739 auto context = static_cast<RequestContext *>(data);
740 if (context == nullptr) {
741 return;
742 }
743 auto undefined = NapiUtils::GetUndefined(context->GetEnv());
744 context->Emit(ON_DATA_END, std::make_pair(undefined, undefined));
745 }
746
ProgressCallback(void * userData,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)747 int HttpExec::ProgressCallback(void *userData, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
748 curl_off_t ulnow)
749 {
750 (void)ultotal;
751 (void)ulnow;
752 auto context = static_cast<RequestContext *>(userData);
753 if (context == nullptr || !context->IsRequestInStream()) {
754 return 0;
755 }
756 if (context->GetManager()->IsEventDestroy()) {
757 return 0;
758 }
759 if (dltotal != 0) {
760 context->SetDlLen(dlnow, dltotal);
761 NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context, OnDataProgress);
762 }
763 return 0;
764 }
765
MakeHeaders(const std::vector<std::string> & vec)766 struct curl_slist *HttpExec::MakeHeaders(const std::vector<std::string> &vec)
767 {
768 struct curl_slist *header = nullptr;
769 std::for_each(vec.begin(), vec.end(), [&header](const std::string &s) {
770 if (!s.empty()) {
771 header = curl_slist_append(header, s.c_str());
772 }
773 });
774 return header;
775 }
776
MakeResponseHeader(napi_env env,void * ctx)777 napi_value HttpExec::MakeResponseHeader(napi_env env, void *ctx)
778 {
779 auto context = reinterpret_cast<RequestContext *>(ctx);
780 (void)env;
781 napi_value header = NapiUtils::CreateObject(context->GetEnv());
782 if (NapiUtils::GetValueType(context->GetEnv(), header) == napi_object) {
783 std::for_each(context->response.GetHeader().begin(), context->response.GetHeader().end(),
784 [context, header](const std::pair<std::string, std::string> &p) {
785 if (!p.first.empty() && !p.second.empty()) {
786 NapiUtils::SetStringPropertyUtf8(context->GetEnv(), header, p.first, p.second);
787 }
788 });
789 }
790 return header;
791 }
792
IsUnReserved(unsigned char in)793 bool HttpExec::IsUnReserved(unsigned char in)
794 {
795 if ((in >= '0' && in <= '9') || (in >= 'a' && in <= 'z') || (in >= 'A' && in <= 'Z')) {
796 return true;
797 }
798 switch (in) {
799 case '-':
800 case '.':
801 case '_':
802 case '~':
803 return true;
804 default:
805 break;
806 }
807 return false;
808 }
809
ProcByExpectDataType(napi_value object,RequestContext * context)810 bool HttpExec::ProcByExpectDataType(napi_value object, RequestContext *context)
811 {
812 switch (context->options.GetHttpDataType()) {
813 case HttpDataType::STRING: {
814 NapiUtils::SetStringPropertyUtf8(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT,
815 context->response.GetResult());
816 NapiUtils::SetUint32Property(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT_TYPE,
817 static_cast<uint32_t>(HttpDataType::STRING));
818 return true;
819 }
820 case HttpDataType::OBJECT: {
821 if (context->response.GetResult().size() > HttpConstant::MAX_JSON_PARSE_SIZE) {
822 return false;
823 }
824
825 napi_value obj = NapiUtils::JsonParse(context->GetEnv(), context->response.GetResult());
826 if (obj) {
827 NapiUtils::SetNamedProperty(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT, obj);
828 NapiUtils::SetUint32Property(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT_TYPE,
829 static_cast<uint32_t>(HttpDataType::OBJECT));
830 return true;
831 }
832
833 // parse maybe failed
834 return false;
835 }
836 case HttpDataType::ARRAY_BUFFER: {
837 void *data = nullptr;
838 auto body = context->response.GetResult();
839 napi_value arrayBuffer = NapiUtils::CreateArrayBuffer(context->GetEnv(), body.size(), &data);
840 if (data != nullptr && arrayBuffer != nullptr) {
841 if (memcpy_s(data, body.size(), body.c_str(), body.size()) < 0) {
842 NETSTACK_LOGE("[ProcByExpectDataType] memory copy failed");
843 return true;
844 }
845 NapiUtils::SetNamedProperty(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT, arrayBuffer);
846 NapiUtils::SetUint32Property(context->GetEnv(), object, HttpConstant::RESPONSE_KEY_RESULT_TYPE,
847 static_cast<uint32_t>(HttpDataType::ARRAY_BUFFER));
848 }
849 return true;
850 }
851 default:
852 break;
853 }
854 return false;
855 }
856
857 #ifndef MAC_PLATFORM
AsyncRunRequest(RequestContext * context)858 void HttpExec::AsyncRunRequest(RequestContext *context)
859 {
860 HttpAsyncWork::ExecRequest(context->GetEnv(), context);
861 }
862 #endif
863
IsInitialized()864 bool HttpExec::IsInitialized()
865 {
866 return staticVariable_.initialized;
867 }
868
DeInitialize()869 void HttpExec::DeInitialize()
870 {
871 std::lock_guard<std::mutex> lock(staticVariable_.curlMultiMutex);
872 staticVariable_.runThread = false;
873 staticVariable_.conditionVariable.notify_all();
874 if (staticVariable_.workThread.joinable()) {
875 staticVariable_.workThread.join();
876 }
877 if (staticVariable_.curlMulti) {
878 curl_multi_cleanup(staticVariable_.curlMulti);
879 }
880 staticVariable_.initialized = false;
881 }
882
ExecFlush(BaseContext * context)883 bool HttpResponseCacheExec::ExecFlush(BaseContext *context)
884 {
885 (void)context;
886 CacheProxy::FlushCache();
887 return true;
888 }
889
FlushCallback(BaseContext * context)890 napi_value HttpResponseCacheExec::FlushCallback(BaseContext *context)
891 {
892 return NapiUtils::GetUndefined(context->GetEnv());
893 }
894
ExecDelete(BaseContext * context)895 bool HttpResponseCacheExec::ExecDelete(BaseContext *context)
896 {
897 (void)context;
898 CacheProxy::StopCacheAndDelete();
899 return true;
900 }
901
DeleteCallback(BaseContext * context)902 napi_value HttpResponseCacheExec::DeleteCallback(BaseContext *context)
903 {
904 return NapiUtils::GetUndefined(context->GetEnv());
905 }
906 } // namespace OHOS::NetStack::Http
907