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