1 /*
2  * Copyright (c) 2024 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 "net_http_client_exec.h"
17 
18 #include <cstddef>
19 #include <cstring>
20 #include <memory>
21 #include <thread>
22 #include <unistd.h>
23 #include <pthread.h>
24 #ifdef HTTP_MULTIPATH_CERT_ENABLE
25 #include <openssl/ssl.h>
26 #endif
27 #if HAS_NETMANAGER_BASE
28 #include <netdb.h>
29 #endif
30 
31 #ifdef HTTP_PROXY_ENABLE
32 #include "parameter.h"
33 #endif
34 #ifdef HAS_NETMANAGER_BASE
35 #include "http_proxy.h"
36 #include "net_conn_client.h"
37 #endif
38 
39 #include "net_http_utils.h"
40 #include "net_http_cache_proxy.h"
41 #include "constant.h"
42 #include "netstack_common_utils.h"
43 #include "netstack_log.h"
44 #include "securec.h"
45 
46 #define NETSTACK_CURL_EASY_SET_OPTION(handle, opt, data, asyncContext)                                   \
47     do {                                                                                                 \
48         CURLcode result = curl_easy_setopt(handle, opt, data);                                           \
49         if (result != CURLE_OK) {                                                                        \
50             const char *err = curl_easy_strerror(result);                                                \
51             NETSTACK_LOGE("Failed to set option: %{public}s, %{public}s %{public}d", #opt, err, result); \
52             (asyncContext)->SetErrorCode(result);                                                        \
53             return false;                                                                                \
54         }                                                                                                \
55     } while (0)
56 
57 namespace OHOS::NetStack::Http {
58 static constexpr int CURL_TIMEOUT_MS = 50;
59 static constexpr int CONDITION_TIMEOUT_S = 3600;
60 static constexpr int CURL_MAX_WAIT_MSECS = 10;
61 static constexpr int CURL_HANDLE_NUM = 10;
62 static constexpr const char *TLS12_SECURITY_CIPHER_SUITE = R"(DEFAULT:!eNULL:!EXPORT)";
63 static constexpr int NETSTACK_NAPI_INTERNAL_ERROR = 2300002;
64 
65 #ifdef HTTP_MULTIPATH_CERT_ENABLE
66 static constexpr const int32_t UID_TRANSFORM_DIVISOR = 200000;
67 static constexpr const char *BASE_PATH = "/data/certificates/user_cacerts/";
68 static constexpr const char *USER_CERT_ROOT_PATH = "/data/certificates/user_cacerts/0/";
69 static constexpr int32_t SYSPARA_MAX_SIZE = 128;
70 static constexpr const char *DEFAULT_HTTP_PROXY_HOST = "NONE";
71 static constexpr const char *DEFAULT_HTTP_PROXY_PORT = "0";
72 static constexpr const char *DEFAULT_HTTP_PROXY_EXCLUSION_LIST = "NONE";
73 static constexpr const char *HTTP_PROXY_HOST_KEY = "persist.netmanager_base.http_proxy.host";
74 static constexpr const char *HTTP_PROXY_PORT_KEY = "persist.netmanager_base.http_proxy.port";
75 static constexpr const char *HTTP_PROXY_EXCLUSIONS_KEY = "persist.netmanager_base.http_proxy.exclusion_list";
76 #endif
77 
AddCurlHandle(CURL * handle,RequestContext * context)78 bool NetHttpClientExec::AddCurlHandle(CURL *handle, RequestContext *context)
79 {
80     if (handle == nullptr || staticVariable_.curlMulti == nullptr) {
81         NETSTACK_LOGE("handle nullptr");
82         return false;
83     }
84 
85     std::thread([context, handle] {
86         std::lock_guard guard(staticVariable_.curlMultiMutex);
87         // Do SetServerSSLCertOption here to avoid blocking the main thread.
88         SetServerSSLCertOption(handle, context);
89         staticVariable_.infoQueue.emplace(context, handle);
90         staticVariable_.conditionVariable.notify_all();
91         {
92             std::lock_guard lockGuard(staticContextSet_.mutexForContextVec);
93             NetHttpClientExec::staticContextSet_.contextSet.emplace(context);
94         }
95     }).detach();
96 
97     return true;
98 }
99 
100 NetHttpClientExec::StaticVariable NetHttpClientExec::staticVariable_; /* NOLINT */
101 NetHttpClientExec::StaticContextVec NetHttpClientExec::staticContextSet_;
102 
ExecRequest(RequestContext * context)103 void NetHttpClientExec::ExecRequest(RequestContext *context)
104 {
105     if (!CommonUtils::HasInternetPermission()) {
106         context->SetPermissionDenied(true);
107         return;
108     }
109     context->options.SetRequestTime(GetNowTimeGMT());
110     CacheProxy proxy(context->options);
111     if (context->IsUsingCache() && proxy.ReadResponseFromCache(context)) {
112         return;
113     }
114     if (!RequestWithoutCache(context)) {
115         context->SetErrorCode(NETSTACK_NAPI_INTERNAL_ERROR);
116         context->SendResponse();
117         delete context;
118         context = nullptr;
119     }
120 }
121 
RequestWithoutCache(RequestContext * context)122 bool NetHttpClientExec::RequestWithoutCache(RequestContext *context)
123 {
124     if (!staticVariable_.initialized) {
125         NETSTACK_LOGE("curl not init");
126         return false;
127     }
128 
129     auto handle = curl_easy_init();
130     if (!handle) {
131         NETSTACK_LOGE("Failed to create fetch task");
132         return false;
133     }
134 
135     std::vector<std::string> vec;
136     std::for_each(context->options.GetHeader().begin(), context->options.GetHeader().end(),
137                   [&vec](const std::pair<std::string, std::string> &p) {
138                       if (!p.second.empty()) {
139                           vec.emplace_back(p.first + HTTP_HEADER_SEPARATOR + p.second);
140                       } else {
141                           vec.emplace_back(p.first + HTTP_HEADER_BLANK_SEPARATOR);
142                       }
143                   });
144     context->SetCurlHeaderList(MakeHeaders(vec));
145 
146     if (!SetOption(handle, context, context->GetCurlHeaderList())) {
147         NETSTACK_LOGE("set option failed");
148         return false;
149     }
150 
151     context->response.SetRequestTime(GetNowTimeGMT());
152 
153     if (!AddCurlHandle(handle, context)) {
154         NETSTACK_LOGE("add handle failed");
155         return false;
156     }
157 
158     return true;
159 }
160 
GetCurlDataFromHandle(CURL * handle,RequestContext * context,CURLMSG curlMsg,CURLcode result)161 bool NetHttpClientExec::GetCurlDataFromHandle(CURL *handle, RequestContext *context, CURLMSG curlMsg, CURLcode result)
162 {
163     if (curlMsg != CURLMSG_DONE) {
164         NETSTACK_LOGE("CURLMSG %{public}s", std::to_string(curlMsg).c_str());
165         context->SetErrorCode(NETSTACK_NAPI_INTERNAL_ERROR);
166         return false;
167     }
168 
169     if (result != CURLE_OK) {
170         context->SetErrorCode(result);
171         NETSTACK_LOGE("CURLcode result %{public}s", std::to_string(result).c_str());
172         return false;
173     }
174 
175     context->response.SetResponseTime(GetNowTimeGMT());
176 
177     int64_t responseCode;
178     CURLcode code = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
179     if (code != CURLE_OK) {
180         context->SetErrorCode(code);
181         return false;
182     }
183     context->response.SetResponseCode(responseCode);
184     if (context->response.GetResponseCode() == static_cast<uint32_t>(ResponseCode::NOT_MODIFIED)) {
185         NETSTACK_LOGI("cache is NOT_MODIFIED, we use the cache");
186         context->SetResponseByCache();
187         return true;
188     }
189     NETSTACK_LOGI("responseCode is %{public}s", std::to_string(responseCode).c_str());
190 
191     struct curl_slist *cookies = nullptr;
192     code = curl_easy_getinfo(handle, CURLINFO_COOKIELIST, &cookies);
193     if (code != CURLE_OK) {
194         context->SetErrorCode(code);
195         return false;
196     }
197 
198     std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)> cookiesHandle(cookies, curl_slist_free_all);
199     while (cookies) {
200         context->response.AppendCookies(cookies->data, strlen(cookies->data));
201         if (cookies->next != nullptr) {
202             context->response.AppendCookies(HTTP_LINE_SEPARATOR,
203                 strlen(HTTP_LINE_SEPARATOR));
204         }
205         cookies = cookies->next;
206     }
207     return true;
208 }
209 
GetTimingFromCurl(CURL * handle,CURLINFO info)210 double NetHttpClientExec::GetTimingFromCurl(CURL *handle, CURLINFO info)
211 {
212     time_t timing;
213     CURLcode result = curl_easy_getinfo(handle, info, &timing);
214     if (result != CURLE_OK) {
215         NETSTACK_LOGE("Failed to get timing: %{public}d, %{public}s", info, curl_easy_strerror(result));
216         return 0;
217     }
218     return TimeUtils::Microseconds2Milliseconds(timing);
219 }
220 
CacheCurlPerformanceTiming(CURL * handle,RequestContext * context)221 void NetHttpClientExec::CacheCurlPerformanceTiming(CURL* handle, RequestContext* context)
222 {
223     context->CachePerformanceTimingItem(
224         RESPONSE_DNS_TIMING, NetHttpClientExec::GetTimingFromCurl(handle, CURLINFO_NAMELOOKUP_TIME_T));
225     context->CachePerformanceTimingItem(
226         RESPONSE_TCP_TIMING, NetHttpClientExec::GetTimingFromCurl(handle, CURLINFO_CONNECT_TIME_T));
227     context->CachePerformanceTimingItem(
228         RESPONSE_TLS_TIMING, NetHttpClientExec::GetTimingFromCurl(handle, CURLINFO_APPCONNECT_TIME_T));
229     context->CachePerformanceTimingItem(
230         RESPONSE_FIRST_SEND_TIMING, NetHttpClientExec::GetTimingFromCurl(handle, CURLINFO_PRETRANSFER_TIME_T));
231     context->CachePerformanceTimingItem(RESPONSE_FIRST_RECEIVE_TIMING,
232         NetHttpClientExec::GetTimingFromCurl(handle, CURLINFO_STARTTRANSFER_TIME_T));
233     context->CachePerformanceTimingItem(
234         RESPONSE_TOTAL_FINISH_TIMING, NetHttpClientExec::GetTimingFromCurl(handle, CURLINFO_TOTAL_TIME_T));
235     context->CachePerformanceTimingItem(
236         RESPONSE_REDIRECT_TIMING, NetHttpClientExec::GetTimingFromCurl(handle, CURLINFO_REDIRECT_TIME_T));
237 }
238 
HandleCurlData(CURLMsg * msg)239 void NetHttpClientExec::HandleCurlData(CURLMsg *msg)
240 {
241     if (msg == nullptr) {
242         return;
243     }
244 
245     auto handle = msg->easy_handle;
246     if (handle == nullptr) {
247         return;
248     }
249 
250     auto it = staticVariable_.contextMap.find(handle);
251     if (it == staticVariable_.contextMap.end()) {
252         NETSTACK_LOGE("can not find context");
253         return;
254     }
255 
256     auto context = it->second;
257     staticVariable_.contextMap.erase(it);
258     if (context == nullptr) {
259         NETSTACK_LOGE("can not find context");
260         return;
261     }
262     NETSTACK_LOGI("priority = %{public}d", context->options.GetPriority());
263     context->SetExecOK(GetCurlDataFromHandle(handle, context, msg->msg, msg->data.result));
264     CacheCurlPerformanceTiming(handle, context);
265     if (context->IsExecOK()) {
266         CacheProxy proxy(context->options);
267         proxy.WriteResponseToCache(context->response);
268     }
269     size_t callbackSize = 0;
270     if (context->IsRequestInStream() && context->streamingCallback != nullptr) {
271         callbackSize = context->streamingCallback->dataEnd.size();
272     }
273     // call onDataEnd
274     if (callbackSize > 0) {
275         for (size_t i = 0; i < callbackSize; i++) {
276             context->streamingCallback->dataEnd[i]();
277         }
278     }
279     context->SendResponse();
280     delete context;
281     context = nullptr;
282 }
283 
MakeUrl(const std::string & url,std::string param,const std::string & extraParam)284 std::string NetHttpClientExec::MakeUrl(const std::string &url, std::string param, const std::string &extraParam)
285 {
286     if (param.empty()) {
287         param += extraParam;
288     } else {
289         param += HTTP_URL_PARAM_SEPARATOR;
290         param += extraParam;
291     }
292 
293     if (param.empty()) {
294         return url;
295     }
296 
297     return url + HTTP_URL_PARAM_START + param;
298 }
299 
300 
MethodForGet(const std::string & method)301 bool NetHttpClientExec::MethodForGet(const std::string &method)
302 {
303     return (method == HTTP_METHOD_HEAD || method == HTTP_METHOD_OPTIONS ||
304             method == HTTP_METHOD_TRACE || method == HTTP_METHOD_GET ||
305             method == HTTP_METHOD_CONNECT);
306 }
307 
MethodForPost(const std::string & method)308 bool NetHttpClientExec::MethodForPost(const std::string &method)
309 {
310     return (method == HTTP_METHOD_POST || method == HTTP_METHOD_PUT ||
311             method == HTTP_METHOD_DELETE);
312 }
313 
EncodeUrlParam(std::string & str)314 bool NetHttpClientExec::EncodeUrlParam(std::string &str)
315 {
316     char encoded[4];
317     std::string encodeOut;
318     size_t length = strlen(str.c_str());
319     for (size_t i = 0; i < length; ++i) {
320         auto c = static_cast<uint8_t>(str.c_str()[i]);
321         if (IsUnReserved(c)) {
322             encodeOut += static_cast<char>(c);
323         } else {
324             if (sprintf_s(encoded, sizeof(encoded), "%%%02X", c) < 0) {
325                 return false;
326             }
327             encodeOut += encoded;
328         }
329     }
330 
331     if (str == encodeOut) {
332         return false;
333     }
334     str = encodeOut;
335     return true;
336 }
337 
AddRequestInfo()338 void NetHttpClientExec::AddRequestInfo()
339 {
340     std::lock_guard guard(staticVariable_.curlMultiMutex);
341     int num = 0;
342     while (!staticVariable_.infoQueue.empty()) {
343         if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
344             break;
345         }
346 
347         auto info = staticVariable_.infoQueue.top();
348         staticVariable_.infoQueue.pop();
349         auto ret = curl_multi_add_handle(staticVariable_.curlMulti, info.handle);
350         if (ret == CURLM_OK) {
351             staticVariable_.contextMap[info.handle] = info.context;
352         }
353 
354         ++num;
355         if (num >= CURL_HANDLE_NUM) {
356             break;
357         }
358     }
359 }
360 
IsContextDeleted(RequestContext * context)361 bool NetHttpClientExec::IsContextDeleted(RequestContext *context)
362 {
363     if (context == nullptr) {
364         return true;
365     }
366     {
367         std::lock_guard<std::mutex> lockGuard(NetHttpClientExec::staticContextSet_.mutexForContextVec);
368         auto it = std::find(NetHttpClientExec::staticContextSet_.contextSet.begin(),
369                             NetHttpClientExec::staticContextSet_.contextSet.end(), context);
370         if (it == NetHttpClientExec::staticContextSet_.contextSet.end()) {
371             NETSTACK_LOGI("context has been deleted in libuv thread");
372             return true;
373         }
374     }
375     return false;
376 }
377 
RunThread()378 void NetHttpClientExec::RunThread()
379 {
380     while (staticVariable_.runThread && staticVariable_.curlMulti != nullptr) {
381         AddRequestInfo();
382         SendRequest();
383         ReadResponse();
384         std::this_thread::sleep_for(std::chrono::milliseconds(CURL_TIMEOUT_MS));
385         std::unique_lock l(staticVariable_.curlMultiMutex);
386         staticVariable_.conditionVariable.wait_for(l, std::chrono::seconds(CONDITION_TIMEOUT_S), [] {
387             return !staticVariable_.infoQueue.empty() || !staticVariable_.contextMap.empty();
388         });
389     }
390 }
391 
SendRequest()392 void NetHttpClientExec::SendRequest()
393 {
394     std::lock_guard guard(staticVariable_.curlMultiMutex);
395 
396     int runningHandle = 0;
397     int num = 0;
398     do {
399         if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
400             break;
401         }
402 
403         auto ret = curl_multi_perform(staticVariable_.curlMulti, &runningHandle);
404 
405         if (runningHandle > 0) {
406             ret = curl_multi_poll(staticVariable_.curlMulti, nullptr, 0, CURL_MAX_WAIT_MSECS, nullptr);
407         }
408 
409         if (ret != CURLM_OK) {
410             return;
411         }
412 
413         ++num;
414         if (num >= CURL_HANDLE_NUM) {
415             break;
416         }
417     } while (runningHandle > 0);
418 }
419 
ReadResponse()420 void NetHttpClientExec::ReadResponse()
421 {
422     std::lock_guard guard(staticVariable_.curlMultiMutex);
423     CURLMsg *msg = nullptr; /* NOLINT */
424     do {
425         if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
426             break;
427         }
428 
429         int leftMsg;
430         msg = curl_multi_info_read(staticVariable_.curlMulti, &leftMsg);
431         if (msg) {
432             if (msg->msg == CURLMSG_DONE) {
433                 HandleCurlData(msg);
434             }
435             if (msg->easy_handle) {
436                 (void)curl_multi_remove_handle(staticVariable_.curlMulti, msg->easy_handle);
437                 (void)curl_easy_cleanup(msg->easy_handle);
438             }
439         }
440     } while (msg);
441 }
442 
GetGlobalHttpProxyInfo(std::string & host,int32_t & port,std::string & exclusions)443 void NetHttpClientExec::GetGlobalHttpProxyInfo(std::string &host, int32_t &port, std::string &exclusions)
444 {
445 #ifdef HTTP_PROXY_ENABLE
446     char httpProxyHost[SYSPARA_MAX_SIZE] = {0};
447     char httpProxyPort[SYSPARA_MAX_SIZE] = {0};
448     char httpProxyExclusions[SYSPARA_MAX_SIZE] = {0};
449     GetParameter(HTTP_PROXY_HOST_KEY, DEFAULT_HTTP_PROXY_HOST, httpProxyHost, sizeof(httpProxyHost));
450     GetParameter(HTTP_PROXY_PORT_KEY, DEFAULT_HTTP_PROXY_PORT, httpProxyPort, sizeof(httpProxyPort));
451     GetParameter(HTTP_PROXY_EXCLUSIONS_KEY, DEFAULT_HTTP_PROXY_EXCLUSION_LIST, httpProxyExclusions,
452                  sizeof(httpProxyExclusions));
453 
454     host = Decode(httpProxyHost);
455     if (host == DEFAULT_HTTP_PROXY_HOST) {
456         host = std::string();
457     }
458     exclusions = httpProxyExclusions;
459     if (exclusions == DEFAULT_HTTP_PROXY_EXCLUSION_LIST) {
460         exclusions = std::string();
461     }
462 
463     port = std::atoi(httpProxyPort);
464 #endif
465 }
466 
GetHttpProxyInfo(RequestContext * context,std::string & host,int32_t & port,std::string & exclusions)467 void NetHttpClientExec::GetHttpProxyInfo(RequestContext *context, std::string &host,
468     int32_t &port, std::string &exclusions)
469 {
470     if (context->options.GetUsingHttpProxyType() == UsingHttpProxyType::USE_DEFAULT) {
471 #ifdef HAS_NETMANAGER_BASE
472         using namespace NetManagerStandard;
473         HttpProxy httpProxy;
474         NetConnClient::GetInstance().GetDefaultHttpProxy(httpProxy);
475         host = httpProxy.GetHost();
476         port = httpProxy.GetPort();
477         exclusions = CommonUtils::ToString(httpProxy.GetExclusionList());
478 #else
479         GetGlobalHttpProxyInfo(host, port, exclusions);
480 #endif
481     } else if (context->options.GetUsingHttpProxyType() == UsingHttpProxyType::USE_SPECIFIED) {
482         context->options.GetSpecifiedHttpProxy(host, port, exclusions);
483     }
484 }
485 
Initialize()486 bool NetHttpClientExec::Initialize()
487 {
488     std::lock_guard<std::mutex> lock(staticVariable_.mutexForInitialize);
489     if (staticVariable_.initialized) {
490         return true;
491     }
492     NETSTACK_LOGI("call curl_global_init");
493     if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
494         NETSTACK_LOGE("Failed to initialize 'curl'");
495         return false;
496     }
497 
498     staticVariable_.curlMulti = curl_multi_init();
499     if (staticVariable_.curlMulti == nullptr) {
500         NETSTACK_LOGE("Failed to initialize 'curl_multi'");
501         return false;
502     }
503 
504     staticVariable_.workThread = std::thread(RunThread);
505 
506     staticVariable_.initialized = true;
507     return staticVariable_.initialized;
508 }
509 
SetOtherOption(CURL * curl,OHOS::NetStack::Http::RequestContext * context)510 bool NetHttpClientExec::SetOtherOption(CURL *curl, OHOS::NetStack::Http::RequestContext *context)
511 {
512     std::string url = context->options.GetUrl();
513     std::string host;
514     std::string exclusions;
515     int32_t port = 0;
516     GetHttpProxyInfo(context, host, port, exclusions);
517     if (!host.empty() && !CommonUtils::IsHostNameExcluded(url, exclusions, ",")) {
518         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXY, host.c_str(), context);
519         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYPORT, port, context);
520         auto curlTunnelValue = (url.find("https://") != std::string::npos) ? 1L : 0L;
521         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTPPROXYTUNNEL, curlTunnelValue, context);
522         auto proxyType = (host.find("https://") != std::string::npos) ? CURLPROXY_HTTPS : CURLPROXY_HTTP;
523         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYTYPE, proxyType, context);
524     }
525     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2, context);
526     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_CIPHER_LIST, TLS12_SECURITY_CIPHER_SUITE, context);
527 #ifdef NETSTACK_PROXY_PASS
528     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYUSERPWD, NETSTACK_PROXY_PASS, context);
529 #endif // NETSTACK_PROXY_PASS
530 
531 #ifdef HTTP_CURL_PRINT_VERBOSE
532     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_VERBOSE, 1L, context);
533 #endif
534 
535 #ifndef WINDOWS_PLATFORM
536     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_ACCEPT_ENCODING, "", context);
537 #endif
538     return true;
539 }
540 
SslCtxFunction(CURL * curl,void * ssl_ctx,void * parm)541 CURLcode SslCtxFunction(CURL *curl, void *ssl_ctx, void *parm)
542 {
543 #ifdef HTTP_MULTIPATH_CERT_ENABLE
544     auto certsPath = static_cast<CertsPath *>(parm);
545     if (certsPath == nullptr) {
546         NETSTACK_LOGE("certsPath is null");
547         return CURLE_SSL_CERTPROBLEM;
548     }
549     if (ssl_ctx == nullptr) {
550         NETSTACK_LOGE("ssl_ctx is null");
551         return CURLE_SSL_CERTPROBLEM;
552     }
553 
554     for (const auto &path : certsPath->certPathList) {
555         if (path.empty() || access(path.c_str(), F_OK) != 0) {
556             NETSTACK_LOGD("certificate directory path is not exist");
557             continue;
558         }
559         if (!SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ssl_ctx), nullptr, path.c_str())) {
560             NETSTACK_LOGE("loading certificates from directory error.");
561             continue;
562         }
563     }
564     if (access(certsPath->certFile.c_str(), F_OK) != 0) {
565         NETSTACK_LOGD("certificate directory path is not exist");
566     } else if (!SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ssl_ctx), certsPath->certFile.c_str(), nullptr)) {
567         NETSTACK_LOGE("loading certificates from context cert error.");
568     }
569 #endif // HTTP_MULTIPATH_CERT_ENABLE
570     return CURLE_OK;
571 }
572 
SetServerSSLCertOption(CURL * curl,OHOS::NetStack::Http::RequestContext * context)573 bool NetHttpClientExec::SetServerSSLCertOption(CURL *curl, OHOS::NetStack::Http::RequestContext *context)
574 {
575 #ifndef NO_SSL_CERTIFICATION
576 #ifdef HAS_NETMANAGER_BASE
577     auto hostname = CommonUtils::GetHostnameFromURL(context->options.GetUrl());
578 #if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM)
579     std::vector<std::string> certs;
580     // add app cert path
581     auto ret = NetManagerStandard::NetConnClient::GetInstance().GetTrustAnchorsForHostName(hostname, certs);
582     if (ret != 0) {
583         NETSTACK_LOGE("GetTrustAnchorsForHostName error. ret [%{public}d]", ret);
584     }
585 #ifdef HTTP_MULTIPATH_CERT_ENABLE
586     // add user cert path
587     certs.emplace_back(USER_CERT_ROOT_PATH);
588     certs.emplace_back(BASE_PATH + std::to_string(getuid() / UID_TRANSFORM_DIVISOR));
589     // add system cert path
590     certs.emplace_back(HTTP_PREPARE_CA_PATH);
591     context->SetCertsPath(std::move(certs), context->options.GetCaPath());
592     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 1L, context);
593     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 2L, context);
594     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_CTX_FUNCTION, SslCtxFunction, context);
595     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_CTX_DATA, &context->GetCertsPath(), context);
596     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CAINFO, nullptr, context);
597 #else
598     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CAINFO, nullptr, context);
599     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 0L, context);
600     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 0L, context);
601 #endif // HTTP_MULTIPATH_CERT_ENABLE
602 #else
603     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 0L, context);
604     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 0L, context);
605 #endif //  !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM)
606     // pin trusted certifcate keys.
607     std::string pins;
608     auto ret1 = NetManagerStandard::NetConnClient::GetInstance().GetPinSetForHostName(hostname, pins);
609     if (ret1 != 0 || pins.empty()) {
610         NETSTACK_LOGD("Get no pinset by host name");
611     } else {
612         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PINNEDPUBLICKEY, pins.c_str(), context);
613     }
614 #else
615     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CAINFO, nullptr, context);
616     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 0L, context);
617     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 0L, context);
618 #endif // HAS_NETMANAGER_BASE
619 #else
620     // in real life, you should buy a ssl certification and rename it to /etc/ssl/cert.pem
621     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 0L, context);
622     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 0L, context);
623 #endif // NO_SSL_CERTIFICATION
624 
625     return true;
626 }
627 
SetMultiPartOption(CURL * curl,RequestContext * context)628 bool NetHttpClientExec::SetMultiPartOption(CURL *curl, RequestContext *context)
629 {
630     auto header =  context->options.GetHeader();
631     auto type = CommonUtils::ToLower(header[HTTP_CONTENT_TYPE]);
632     if (type != HTTP_CONTENT_TYPE_MULTIPART) {
633         return true;
634     }
635     auto multiPartDataList = context->options.GetMultiPartDataList();
636     if (multiPartDataList.empty()) {
637         return true;
638     }
639     curl_mime *multipart = curl_mime_init(curl);
640     if (multipart == nullptr) {
641         return false;
642     }
643     context->SetMultipart(multipart);
644     curl_mimepart *part = nullptr;
645     bool hasData = false;
646     for (auto &multiFormData : multiPartDataList) {
647         if (multiFormData.name.empty()) {
648             continue;
649         }
650         if (multiFormData.data.empty() && multiFormData.filePath.empty()) {
651             NETSTACK_LOGE("Failed to set multiFormData error no data and filepath at the same time");
652             continue;
653         }
654         part = curl_mime_addpart(multipart);
655         SetFormDataOption(multiFormData, part, curl, context);
656         hasData = true;
657     }
658     if (hasData) {
659         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_MIMEPOST, multipart, context);
660     }
661     return true;
662 }
663 
SetFormDataOption(MultiFormData & multiFormData,curl_mimepart * part,CURL * curl,RequestContext * context)664 void NetHttpClientExec::SetFormDataOption(MultiFormData &multiFormData, curl_mimepart *part,
665     CURL *curl, RequestContext *context)
666 {
667     CURLcode result = curl_mime_name(part, multiFormData.name.c_str());
668     if (result != CURLE_OK) {
669         NETSTACK_LOGE("Failed to set name error: %{public}s", curl_easy_strerror(result));
670         return;
671     }
672     if (!multiFormData.contentType.empty()) {
673         result = curl_mime_type(part, multiFormData.contentType.c_str());
674         if (result != CURLE_OK) {
675             NETSTACK_LOGE("Failed to set contentType error: %{public}s", curl_easy_strerror(result));
676         }
677     }
678     if (!multiFormData.remoteFileName.empty()) {
679         result = curl_mime_filename(part, multiFormData.remoteFileName.c_str());
680         if (result != CURLE_OK) {
681             NETSTACK_LOGE("Failed to set remoteFileName error: %{public}s", curl_easy_strerror(result));
682         }
683     }
684     if (!multiFormData.data.empty()) {
685         result = curl_mime_data(part, multiFormData.data.c_str(), CURL_ZERO_TERMINATED);
686         if (result != CURLE_OK) {
687             NETSTACK_LOGE("Failed to set data error: %{public}s", curl_easy_strerror(result));
688         }
689     } else {
690         result = curl_mime_filedata(part, multiFormData.filePath.c_str());
691         if (result != CURLE_OK) {
692             NETSTACK_LOGE("Failed to set file data error: %{public}s", curl_easy_strerror(result));
693         }
694     }
695 }
696 
SetSSLCertOption(CURL * curl,OHOS::NetStack::Http::RequestContext * context)697 bool NetHttpClientExec::SetSSLCertOption(CURL *curl, OHOS::NetStack::Http::RequestContext *context)
698 {
699     std::string cert;
700     std::string certType;
701     std::string key;
702     SecureChar keyPasswd;
703     context->options.GetClientCert(cert, certType, key, keyPasswd);
704     if (cert.empty()) {
705         NETSTACK_LOGI("SetSSLCertOption param is empty.");
706         return false;
707     }
708     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSLCERT, cert.c_str(), context);
709     if (!key.empty()) {
710         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSLKEY, key.c_str(), context);
711     }
712     if (!certType.empty()) {
713         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSLCERTTYPE, certType.c_str(), context);
714     }
715     if (keyPasswd.Length() > 0) {
716         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_KEYPASSWD, keyPasswd.Data(), context);
717     }
718     return true;
719 }
720 
SetDnsOption(CURL * curl,RequestContext * context)721 bool NetHttpClientExec::SetDnsOption(CURL *curl, RequestContext *context)
722 {
723     std::vector<std::string> dnsServers = context->options.GetDnsServers();
724     if (dnsServers.empty()) {
725         return true;
726     }
727     std::string serverList;
728     for (auto &server : dnsServers) {
729         serverList += server + ",";
730         NETSTACK_LOGI("SetDns server: %{public}s", CommonUtils::AnonymizeIp(server).c_str());
731     }
732     serverList.pop_back();
733     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_DNS_SERVERS, serverList.c_str(), context);
734     return true;
735 }
736 
SetRequestOption(CURL * curl,RequestContext * context)737 bool NetHttpClientExec::SetRequestOption(CURL *curl, RequestContext *context)
738 {
739     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTP_VERSION, context->options.GetHttpVersion(), context);
740     const std::string range = context->options.GetRangeString();
741     if (range.empty()) {
742         // Some servers don't like requests that are made without a user-agent field, so we provide one
743         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_USERAGENT, HTTP_DEFAULT_USER_AGENT, context);
744     } else {
745         // https://curl.se/libcurl/c/CURLOPT_RANGE.html
746         if (context->options.GetMethod() == HTTP_METHOD_PUT) {
747             context->SetErrorCode(CURLE_RANGE_ERROR);
748             NETSTACK_LOGE("For HTTP PUT uploads this option should not be used, since it may conflict with \
749                           other options.");
750             return false;
751         }
752         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_RANGE, range.c_str(), context);
753     }
754     if (!context->options.GetDohUrl().empty()) {
755         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_DOH_URL, context->options.GetDohUrl().c_str(), context);
756     }
757     SetDnsOption(curl, context);
758     SetSSLCertOption(curl, context);
759     SetMultiPartOption(curl, context);
760     return true;
761 }
762 
SetOption(CURL * curl,RequestContext * context,struct curl_slist * requestHeader)763 bool NetHttpClientExec::SetOption(CURL *curl, RequestContext *context, struct curl_slist *requestHeader)
764 {
765     const std::string &method = context->options.GetMethod();
766     if (!MethodForGet(method) && !MethodForPost(method)) {
767         NETSTACK_LOGE("method %{public}s not supported", method.c_str());
768         return false;
769     }
770 
771     if (context->options.GetMethod() == HTTP_METHOD_HEAD) {
772         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOBODY, 1L, context);
773     }
774 
775     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, context->options.GetUrl().c_str(), context);
776     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CUSTOMREQUEST, method.c_str(), context);
777 
778     if (MethodForPost(method) && !context->options.GetBody().empty()) {
779         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POST, 1L, context);
780         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDS, context->options.GetBody().c_str(), context);
781         NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDSIZE, context->options.GetBody().size(), context);
782     }
783 
784     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback, context);
785     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_XFERINFODATA, context, context);
786     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOPROGRESS, 0L, context);
787 
788     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEFUNCTION, OnWritingMemoryBody, context);
789     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEDATA, context, context);
790 
791     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader, context);
792     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERDATA, context, context);
793     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTPHEADER, requestHeader, context);
794     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_FOLLOWLOCATION, 1L, context);
795 
796     /* first #undef CURL_DISABLE_COOKIES in curl config */
797     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_COOKIEFILE, "", context);
798     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOSIGNAL, 1L, context);
799     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_TIMEOUT_MS, context->options.GetReadTimeout(), context);
800     NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CONNECTTIMEOUT_MS, context->options.GetConnectTimeout(), context);
801 
802     SetRequestOption(curl, context);
803 
804     if (!SetOtherOption(curl, context)) {
805         return false;
806     }
807     return true;
808 }
809 
OnWritingMemoryBody(const void * data,size_t size,size_t memBytes,void * userData)810 size_t NetHttpClientExec::OnWritingMemoryBody(const void *data, size_t size, size_t memBytes, void *userData)
811 {
812     auto context = static_cast<RequestContext *>(userData);
813     if (context == nullptr) {
814         return 0;
815     }
816     if (context->IsDestroyed()) {
817         context->StopAndCachePerformanceTiming(RESPONSE_BODY_TIMING);
818         return 0;
819     }
820     size_t callbackSize = 0;
821     if (context->streamingCallback != nullptr) {
822         callbackSize = context->streamingCallback->dataReceive.size();
823     }
824     if (context->IsRequestInStream() && callbackSize > 0) {
825         context->SetTempData(data, size * memBytes);
826         // call OnDataReceive
827         auto tmp = context->GetTempData();
828         context->PopTempData();
829         for (size_t i = 0; i < callbackSize; i++) {
830             CArrUI8 body;
831             body.head = reinterpret_cast<uint8_t*>(MallocCString(tmp));
832             body.size = static_cast<int64_t>(tmp.size());
833             context->streamingCallback->dataReceive[i](body);
834         }
835         context->StopAndCachePerformanceTiming(RESPONSE_BODY_TIMING);
836         return size * memBytes;
837     }
838     if (context->response.GetResult().size() > context->options.GetMaxLimit()) {
839         NETSTACK_LOGE("response data exceeds the maximum limit");
840         context->StopAndCachePerformanceTiming(RESPONSE_BODY_TIMING);
841         return 0;
842     }
843     context->response.AppendResult(data, size * memBytes);
844     context->StopAndCachePerformanceTiming(RESPONSE_BODY_TIMING);
845     return size * memBytes;
846 }
847 
MakeHeaderWithSetCookie(RequestContext * context)848 static std::map<std::string, std::string> MakeHeaderWithSetCookie(RequestContext *context)
849 {
850     std::map<std::string, std::string> tempMap = context->response.GetHeader();
851     std::string setCookies;
852     size_t loop = 0;
853     for (const auto &setCookie : context->response.GetsetCookie()) {
854         setCookies += setCookie;
855         if (loop + 1 < context->response.GetsetCookie().size()) {
856             setCookies += HTTP_LINE_SEPARATOR;
857         }
858         ++loop;
859     }
860     tempMap[RESPONSE_KEY_SET_COOKIE] = setCookies;
861     return tempMap;
862 }
863 
OnWritingMemoryHeader(const void * data,size_t size,size_t memBytes,void * userData)864 size_t NetHttpClientExec::OnWritingMemoryHeader(const void *data, size_t size, size_t memBytes, void *userData)
865 {
866     auto context = static_cast<RequestContext *>(userData);
867     if (context == nullptr) {
868         return 0;
869     }
870     if (context->IsDestroyed()) {
871         context->StopAndCachePerformanceTiming(RESPONSE_HEADER_TIMING);
872         return 0;
873     }
874     if (context->response.GetResult().size() > context->options.GetMaxLimit()) {
875         NETSTACK_LOGE("response data exceeds the maximum limit");
876         context->StopAndCachePerformanceTiming(RESPONSE_HEADER_TIMING);
877         return 0;
878     }
879     context->response.AppendRawHeader(data, size * memBytes);
880     if (CommonUtils::EndsWith(context->response.GetRawHeader(), HTTP_RESPONSE_HEADER_SEPARATOR)) {
881         context->response.ParseHeaders();
882         int callbackSize = 0;
883         int callOnceSize = 0;
884         if (context->streamingCallback) {
885             callbackSize = static_cast<int>(context->streamingCallback->headersReceive.size());
886             callOnceSize = static_cast<int>(context->streamingCallback->headersReceiveOnce.size());
887         }
888 
889         // call onHeadersReceive
890         if (!context->IsDestroyed() && (callbackSize > 0 || callOnceSize > 0)) {
891             auto headersMap = MakeHeaderWithSetCookie(context);
892             for (int i = 0; i < callbackSize; i++) {
893                 auto ret = g_map2CArrString(headersMap);
894                 context->streamingCallback->headersReceive[i](ret);
895             }
896             for (int i = 0; i < callOnceSize; i++) {
897                 auto ret = g_map2CArrString(headersMap);
898                 context->streamingCallback->headersReceiveOnce[i](ret);
899             }
900             context->streamingCallback->headersReceiveOnce.clear();
901         }
902     }
903     context->StopAndCachePerformanceTiming(RESPONSE_HEADER_TIMING);
904     return size * memBytes;
905 }
906 
MakeHeaders(const std::vector<std::string> & vec)907 struct curl_slist *NetHttpClientExec::MakeHeaders(const std::vector<std::string> &vec)
908 {
909     struct curl_slist *header = nullptr;
910     std::for_each(vec.begin(), vec.end(), [&header](const std::string &s) {
911         if (!s.empty()) {
912             header = curl_slist_append(header, s.c_str());
913         }
914     });
915     return header;
916 }
917 
ProgressCallback(void * userData,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)918 int NetHttpClientExec::ProgressCallback(void *userData, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
919     curl_off_t ulnow)
920 {
921     auto context = static_cast<RequestContext*>(userData);
922     if (context == nullptr) {
923         return 0;
924     }
925     if (ultotal != 0 && ultotal >= ulnow && !context->CompareWithLastElement(ulnow, ultotal)) {
926         context->SetUlLen(ulnow, ultotal);
927         size_t callbackSize = 0;
928         if (context->streamingCallback != nullptr) {
929             callbackSize = context->streamingCallback->dataSendProgress.size();
930         }
931         // call OnDataUploadProgress
932         if (!IsContextDeleted(context) && callbackSize > 0) {
933             auto ulLen = context->GetUlLen();
934             for (size_t i = 0; i < callbackSize; i++) {
935                 CDataSendProgressInfo info = {.sendSize = ulLen.nLen, .totalSize = ulLen.tLen};
936                 context->streamingCallback->dataSendProgress[i](info);
937             }
938         }
939     }
940     if (!context->IsRequestInStream()) {
941         return 0;
942     }
943     if (dltotal != 0) {
944         context->SetDlLen(dlnow, dltotal);
945         int callbackSize = 0;
946         if (context->streamingCallback != nullptr) {
947             callbackSize = static_cast<int>(context->streamingCallback->dataReceiveProgress.size());
948         }
949 
950         // call OnDataProgress
951         if (!IsContextDeleted(context) && callbackSize > 0 && dlnow != 0) {
952             auto dlLen = context->GetDlLen();
953             for (int i = 0; i < callbackSize; i++) {
954                 CDataReceiveProgressInfo info = {.receiveSize = dlLen.nLen, .totalSize = dlLen.tLen};
955                 context->streamingCallback->dataReceiveProgress[i](info);
956             }
957         }
958     }
959     return 0;
960 }
961 
IsUnReserved(unsigned char in)962 bool NetHttpClientExec::IsUnReserved(unsigned char in)
963 {
964     if ((in >= '0' && in <= '9') || (in >= 'a' && in <= 'z') || (in >= 'A' && in <= 'Z')) {
965         return true;
966     }
967     switch (in) {
968         case '-':
969         case '.':
970         case '_':
971         case '~':
972             return true;
973         default:
974             break;
975     }
976     return false;
977 }
978 
IsInitialized()979 bool NetHttpClientExec::IsInitialized()
980 {
981     return staticVariable_.initialized;
982 }
983 
DeInitialize()984 void NetHttpClientExec::DeInitialize()
985 {
986     std::lock_guard<std::mutex> lock(staticVariable_.curlMultiMutex);
987     staticVariable_.runThread = false;
988     staticVariable_.conditionVariable.notify_all();
989     if (staticVariable_.workThread.joinable()) {
990         staticVariable_.workThread.join();
991     }
992     if (staticVariable_.curlMulti) {
993         curl_multi_cleanup(staticVariable_.curlMulti);
994     }
995     staticVariable_.initialized = false;
996 }
997 }