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