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 ®ex)
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 }