1 /*
2  * Copyright (c) 2023-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 #define HST_LOG_TAG "HttpCurlClient"
16 
17 #include "http_curl_client.h"
18 #include <algorithm>
19 #include <regex>
20 #include <vector>
21 #include "common/log.h"
22 #include "osal/task/autolock.h"
23 #include "securec.h"
24 #include "net_conn_client.h"
25 #include <fcntl.h>
26 
27 namespace {
28 constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, LOG_DOMAIN_STREAM_SOURCE, "HiStreamer" };
29 }
30 
31 namespace OHOS {
32 namespace Media {
33 namespace Plugins {
34 namespace HttpPlugin {
35 const uint32_t MAX_STRING_LENGTH = 4096;
36 constexpr uint32_t DEFAULT_LOW_SPEED_LIMIT = 1L;
37 constexpr uint32_t DEFAULT_LOW_SPEED_TIME = 10L;
38 constexpr uint32_t MILLS_TO_SECOND = 1000;
39 constexpr uint32_t HTTP_ERROR_THRESHOLD = 400;
40 
ToString(const std::list<std::string> & lists,char tab)41 std::string ToString(const std::list<std::string> &lists, char tab)
42 {
43     std::string str;
44     for (auto it = lists.begin(); it != lists.end(); ++it) {
45         if (it != lists.begin()) {
46             str.append(1, tab);
47         }
48         str.append(*it);
49     }
50     return str;
51 }
52 
InsertCharBefore(std::string input,char from,char preChar,char nextChar)53 std::string InsertCharBefore(std::string input, char from, char preChar, char nextChar)
54 {
55     std::string output = input;
56     char arr[] = {preChar, from};
57     unsigned long strSize = sizeof(arr) / sizeof(arr[0]);
58     std::string str(arr, strSize);
59     std::size_t pos = output.find(from);
60     std::size_t length = output.length();
61     while (pos >= 0 && length >= 1 && pos <= length - 1 && pos != std::string::npos) {
62         char nextCharTemp = pos >= length ? '\0' : output[pos + 1];
63         if (nextChar == '\0' || nextCharTemp == '\0' || nextCharTemp != nextChar) {
64             output.replace(pos, 1, str);
65             length += (strSize - 1);
66         }
67         pos = output.find(from, pos + strSize);
68     }
69     return output;
70 }
71 
Trim(std::string str)72 std::string Trim(std::string str)
73 {
74     if (str.empty()) {
75         return str;
76     }
77     while (std::isspace(str[0])) {
78         str.erase(0, 1);
79     }
80     if (str.empty()) {
81         return str;
82     }
83     while (str.size() >= 1 && std::isspace(str[str.size() - 1])) {
84             str.erase(str.size() - 1, 1);
85     }
86     return str;
87 }
88 
GetHostnameFromURL(const std::string & url)89 std::string GetHostnameFromURL(const std::string &url)
90 {
91     std::string delimiter = "://";
92     std::string tempUrl = url;
93     size_t posStart = tempUrl.find(delimiter);
94     if (posStart != std::string::npos) {
95         posStart += delimiter.length();
96     } else {
97         posStart = 0;
98     }
99     size_t posEnd = std::min({tempUrl.find(":", posStart), tempUrl.find("/", posStart), tempUrl.find("?", posStart)});
100     if (posEnd != std::string::npos) {
101         return tempUrl.substr(posStart, posEnd - posStart);
102     }
103     return tempUrl.substr(posStart);
104 }
105 
IsRegexValid(const std::string & regex)106 bool IsRegexValid(const std::string &regex)
107 {
108     if (Trim(regex).empty()) {
109         return false;
110     }
111     return regex_match(regex, std::regex("^[a-zA-Z0-9\\-_\\.*]+$"));
112 }
113 
ReplaceCharacters(const std::string & input)114 std::string ReplaceCharacters(const std::string &input)
115 {
116     std::string output = InsertCharBefore(input, '*', '.', '\0');
117     output = InsertCharBefore(output, '.', '\\', '*');
118     return output;
119 }
120 
IsMatch(const std::string & str,const std::string & patternStr)121 bool IsMatch(const std::string &str, const std::string &patternStr)
122 {
123     if (patternStr.empty()) {
124         return false;
125     }
126     if (patternStr == "*") {
127         return true;
128     }
129     if (!IsRegexValid(patternStr)) {
130         return patternStr == str;
131     }
132     std::regex pattern(ReplaceCharacters(patternStr));
133     bool isMatch = patternStr != "" && std::regex_match(str, pattern);
134     return isMatch;
135 }
136 
IsExcluded(const std::string & str,const std::string & exclusions,const std::string & split)137 bool IsExcluded(const std::string &str, const std::string &exclusions, const std::string &split)
138 {
139     if (Trim(exclusions).empty()) {
140         return false;
141     }
142     std::size_t start = 0;
143     std::size_t end = exclusions.find(split);
144     while (end != std::string::npos) {
145         if (end - start > 0 && IsMatch(str, Trim(exclusions.substr(start, end - start)))) {
146             return true;
147         }
148         start = end + 1;
149         end = exclusions.find(split, start);
150     }
151     return IsMatch(str, Trim(exclusions.substr(start)));
152 }
153 
IsHostNameExcluded(const std::string & url,const std::string & exclusions,const std::string & split)154 bool IsHostNameExcluded(const std::string &url, const std::string &exclusions, const std::string &split)
155 {
156     std::string hostName = GetHostnameFromURL(url);
157     return IsExcluded(hostName, exclusions, split);
158 }
159 
GetHttpProxyInfo(std::string & host,int32_t & port,std::string & exclusions)160 void GetHttpProxyInfo(std::string &host, int32_t &port, std::string &exclusions)
161 {
162     using namespace NetManagerStandard;
163     NetManagerStandard::HttpProxy httpProxy;
164     NetConnClient::GetInstance().GetDefaultHttpProxy(httpProxy);
165     host = httpProxy.GetHost();
166     port = httpProxy.GetPort();
167     exclusions = ToString(httpProxy.GetExclusionList());
168 }
169 
GetInstance(RxHeader headCallback,RxBody bodyCallback,void * userParam)170 std::shared_ptr<NetworkClient> NetworkClient::GetInstance(RxHeader headCallback, RxBody bodyCallback, void *userParam)
171 {
172     return std::make_shared<HttpCurlClient>(headCallback, bodyCallback, userParam);
173 }
174 
HttpCurlClient(RxHeader headCallback,RxBody bodyCallback,void * userParam)175 HttpCurlClient::HttpCurlClient(RxHeader headCallback, RxBody bodyCallback, void *userParam)
176     : rxHeader_(headCallback), rxBody_(bodyCallback), userParam_(userParam)
177 {
178     MEDIA_LOG_I("HttpCurlClient ctor");
179 }
180 
~HttpCurlClient()181 HttpCurlClient::~HttpCurlClient()
182 {
183     MEDIA_LOG_I("~HttpCurlClient dtor");
184     Close(false);
185 }
186 
Init()187 Status HttpCurlClient::Init()
188 {
189     FALSE_LOG(curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK);
190     return Status::OK;
191 }
192 
ClearHeadTailSpace(std::string & str)193 std::string HttpCurlClient::ClearHeadTailSpace(std::string& str)
194 {
195     if (str.empty()) {
196         return str;
197     }
198     str.erase(0, str.find_first_not_of(" "));
199     str.erase(str.find_last_not_of(" ") + 1);
200     return str;
201 }
202 
HttpHeaderParse(std::map<std::string,std::string> httpHeader)203 void HttpCurlClient::HttpHeaderParse(std::map<std::string, std::string> httpHeader)
204 {
205     if (httpHeader.empty()) {
206         MEDIA_LOG_D("Set http header fail, http header is empty.");
207         return;
208     }
209     for (std::map<std::string, std::string>::iterator iter = httpHeader.begin(); iter != httpHeader.end(); iter++) {
210         std::string setKey = iter->first;
211         std::string setValue = iter->second;
212         if (setKey.length() <= MAX_STRING_LENGTH && setValue.length() <= MAX_STRING_LENGTH) {
213             ClearHeadTailSpace(setKey);
214             std::string headerStr = setKey + ":" + setValue;
215             const char* str = headerStr.c_str();
216             headerList_ = curl_slist_append(headerList_, str);
217         } else {
218             MEDIA_LOG_E("Set httpHeader fail, the length of key or value is too long, more than 512.");
219             MEDIA_LOG_E("key: " PUBLIC_LOG_S " value: " PUBLIC_LOG_S, setKey.c_str(), setValue.c_str());
220         }
221     }
222 }
223 
Open(const std::string & url,const std::map<std::string,std::string> & httpHeader,int32_t timeoutMs)224 Status HttpCurlClient::Open(const std::string& url, const std::map<std::string, std::string>& httpHeader,
225                             int32_t timeoutMs)
226 {
227     MEDIA_LOG_I("Open client in");
228     if (easyHandle_ == nullptr) {
229         MEDIA_LOG_E("EasyHandle is nullptr, init easyHandle.");
230         easyHandle_ = curl_easy_init();
231     }
232     FALSE_RETURN_V(easyHandle_ != nullptr, Status::ERROR_NULL_POINTER);
233     std::map<std::string, std::string> header = httpHeader;
234     if (isFirstOpen_) {
235         HttpHeaderParse(header);
236         isFirstOpen_ = false;
237     }
238     InitCurlEnvironment(url, timeoutMs);
239     MEDIA_LOG_I("Open client out");
240     return Status::OK;
241 }
242 
Close(bool isAsync)243 Status HttpCurlClient::Close(bool isAsync)
244 {
245     MEDIA_LOG_I("Close client in");
246     {
247         AutoLock lock(mutex_);
248         if (easyHandle_) {
249             curl_easy_setopt(easyHandle_, CURLOPT_TIMEOUT_MS, 1);
250         }
251     }
252     if (isAsync) {
253         MEDIA_LOG_I("Close client Async out");
254         return Status::OK;
255     }
256     AutoLock lock(mutex_);
257     if (easyHandle_) {
258         curl_easy_cleanup(easyHandle_);
259         easyHandle_ = nullptr;
260     }
261     ipFlag_ = false;
262     if (!ip_.empty()) {
263         ip_.clear();
264     }
265     MEDIA_LOG_I("Close client out");
266     return Status::OK;
267 }
268 
Deinit()269 Status HttpCurlClient::Deinit()
270 {
271     MEDIA_LOG_I("Deinit in");
272     AutoLock lock(mutex_);
273     if (easyHandle_) {
274         curl_easy_setopt(easyHandle_, CURLOPT_TIMEOUT_MS, 1);
275     }
276     if (easyHandle_) {
277         curl_easy_cleanup(easyHandle_);
278         easyHandle_ = nullptr;
279     }
280     ipFlag_ = false;
281     if (!ip_.empty()) {
282         ip_.clear();
283     }
284     curl_global_cleanup();
285     MEDIA_LOG_I("Deinit out");
286     return Status::OK;
287 }
288 
GetIp(std::string & ip)289 Status HttpCurlClient::GetIp(std::string &ip)
290 {
291     if (!ip_.empty()) {
292         std::string obj(ip_);
293         ip = obj;
294     } else {
295         MEDIA_LOG_E("Get ip failed, ip is null.");
296     }
297     return Status::OK;
298 }
299 
InitCurProxy(const std::string & url)300 void HttpCurlClient::InitCurProxy(const std::string& url)
301 {
302     std::string host;
303     std::string exclusions;
304     int32_t port = 0;
305     GetHttpProxyInfo(host, port, exclusions);
306     if (!host.empty() && !IsHostNameExcluded(url, exclusions, ",")) {
307         MEDIA_LOG_I("InitCurlEnvironment host: " PUBLIC_LOG_S ", port " PUBLIC_LOG_U32 ", exclusions " PUBLIC_LOG_S,
308             host.c_str(), port, exclusions.c_str());
309         curl_easy_setopt(easyHandle_, CURLOPT_PROXY, host.c_str());
310         curl_easy_setopt(easyHandle_, CURLOPT_PROXYPORT, port);
311         auto curlTunnelValue = (url.find("https://") != std::string::npos) ? 1L : 0L;
312         curl_easy_setopt(easyHandle_, CURLOPT_HTTPPROXYTUNNEL, curlTunnelValue);
313         auto proxyType = (host.find("https://") != std::string::npos) ? CURLPROXY_HTTPS : CURLPROXY_HTTP;
314         curl_easy_setopt(easyHandle_, CURLOPT_PROXYTYPE, proxyType);
315     } else {
316         if (IsHostNameExcluded(url, exclusions, ",")) {
317             MEDIA_LOG_I("InitCurlEnvironment host name is excluded.");
318         }
319     }
320 }
321 
InitCurlEnvironment(const std::string & url,int32_t timeoutMs)322 void HttpCurlClient::InitCurlEnvironment(const std::string& url, int32_t timeoutMs)
323 {
324     curl_easy_setopt(easyHandle_, CURLOPT_URL, UrlParse(url).c_str());
325     curl_easy_setopt(easyHandle_, CURLOPT_CONNECTTIMEOUT, 5); // 5
326     curl_easy_setopt(easyHandle_, CURLOPT_SSL_VERIFYPEER, 0L);
327     curl_easy_setopt(easyHandle_, CURLOPT_SSL_VERIFYHOST, 0L);
328 #ifndef CA_DIR
329     curl_easy_setopt(easyHandle_, CURLOPT_CAINFO, "/etc/ssl/certs/" "cacert.pem");
330 #else
331     curl_easy_setopt(easyHandle_, CURLOPT_CAINFO, CA_DIR "cacert.pem");
332 #endif
333     curl_easy_setopt(easyHandle_, CURLOPT_HTTPGET, 1L);
334     curl_easy_setopt(easyHandle_, CURLOPT_FORBID_REUSE, 0L);
335     curl_easy_setopt(easyHandle_, CURLOPT_FOLLOWLOCATION, 1L);
336     curl_easy_setopt(easyHandle_, CURLOPT_WRITEFUNCTION, rxBody_);
337     curl_easy_setopt(easyHandle_, CURLOPT_WRITEDATA, userParam_);
338     curl_easy_setopt(easyHandle_, CURLOPT_HEADERFUNCTION, rxHeader_);
339     curl_easy_setopt(easyHandle_, CURLOPT_HEADERDATA, userParam_);
340     curl_easy_setopt(easyHandle_, CURLOPT_TCP_KEEPALIVE, 1L);
341     curl_easy_setopt(easyHandle_, CURLOPT_TCP_KEEPINTVL, 5L); // 5 心跳
342     int32_t timeout = timeoutMs > 0 ? timeoutMs / MILLS_TO_SECOND : DEFAULT_LOW_SPEED_TIME;
343     curl_easy_setopt(easyHandle_, CURLOPT_LOW_SPEED_LIMIT, DEFAULT_LOW_SPEED_LIMIT);
344     curl_easy_setopt(easyHandle_, CURLOPT_LOW_SPEED_TIME, timeout);
345     InitCurProxy(url);
346 }
347 
UrlParse(const std::string & url) const348 std::string HttpCurlClient::UrlParse(const std::string& url) const
349 {
350     std::string s;
351     std::regex_replace(std::back_inserter(s), url.begin(), url.end(), std::regex(" "), "%20");
352     return s;
353 }
354 
CheckRequestRange(long startPos,int len)355 void HttpCurlClient::CheckRequestRange(long startPos, int len)
356 {
357     if (startPos >= 0) {
358         char requestRange[128] = {0};
359         if (len > 0) {
360             snprintf_s(requestRange, sizeof(requestRange), sizeof(requestRange) - 1, "%ld-%ld",
361                        startPos, startPos + len - 1);
362         } else {
363             snprintf_s(requestRange, sizeof(requestRange), sizeof(requestRange) - 1, "%ld-", startPos);
364         }
365         MEDIA_LOG_DD("RequestData: requestRange " PUBLIC_LOG_S, requestRange);
366         std::string requestStr(requestRange);
367         AutoLock lock(mutex_);
368         if (easyHandle_) {
369             curl_easy_setopt(easyHandle_, CURLOPT_RANGE, requestStr.c_str());
370         }
371     }
372 }
373 
374 // RequestData run in HttpDownload thread,
375 // Open, Close, Deinit run in other thread.
376 // Should call Open before start HttpDownload thread.
377 // Should Pause HttpDownload thread then Close, Deinit.
RequestData(long startPos,int len,const RequestInfo & requestInfo,HandleResponseCbFunc completedCb)378 Status HttpCurlClient::RequestData(long startPos, int len, const RequestInfo& requestInfo,
379     HandleResponseCbFunc completedCb)
380 {
381     FALSE_RETURN_V(easyHandle_ != nullptr, Status::ERROR_NULL_POINTER);
382     CheckRequestRange(startPos, len);
383     if (isFirstRequest_) {
384         headerList_ = curl_slist_append(headerList_, "Accept: */*");
385         headerList_ = curl_slist_append(headerList_, "Connection: Keep-alive");
386         headerList_ = curl_slist_append(headerList_, "Keep-Alive: timeout=120");
387         isFirstRequest_ = false;
388     }
389     int32_t clientCode = 0;
390     int32_t serverCode = 0;
391     Status ret = Status::OK;
392     {
393         AutoLock lock(mutex_);
394         FALSE_RETURN_V(easyHandle_ != nullptr, Status::ERROR_NULL_POINTER);
395         curl_easy_setopt(easyHandle_, CURLOPT_HTTPHEADER, headerList_);
396         MEDIA_LOG_D("RequestData: startPos " PUBLIC_LOG_D32 ", len " PUBLIC_LOG_D32, static_cast<int>(startPos), len);
397         CURLcode returnCode = curl_easy_perform(easyHandle_);
398         if (returnCode != CURLE_OK) {
399             MEDIA_LOG_E("Curl error " PUBLIC_LOG_D32, returnCode);
400             clientCode = returnCode;
401             ret = Status::ERROR_CLIENT;
402         } else {
403             int64_t httpCode = 0;
404             curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &httpCode);
405             if (httpCode >= HTTP_ERROR_THRESHOLD) {
406                 MEDIA_LOG_E("Http error " PUBLIC_LOG_D64, httpCode);
407                 serverCode = httpCode;
408                 ret = Status::ERROR_SERVER;
409             }
410             SetIp();
411         }
412     }
413     completedCb(clientCode, serverCode, ret);
414     return ret;
415 }
416 
SetIp()417 Status HttpCurlClient::SetIp()
418 {
419     FALSE_RETURN_V(easyHandle_ != nullptr, Status::ERROR_NULL_POINTER);
420     Status retSetIp = Status::OK;
421     if (!ipFlag_) {
422         char* ip = nullptr;
423         if (!curl_easy_getinfo(easyHandle_, CURLINFO_PRIMARY_IP, &ip) && ip) {
424             ip_ = ip;
425             ipFlag_ = true;
426         } else {
427             ip_ = "";
428             MEDIA_LOG_E("Set sever ip failed.");
429             retSetIp = Status::ERROR_UNKNOWN;
430         }
431     }
432     return retSetIp;
433 }
434 
SetAppUid(int32_t appUid)435 void HttpCurlClient::SetAppUid(int32_t appUid)
436 {
437     appUid_ = appUid;
438 }
439 }
440 }
441 }
442 }