1 /*
2  * Copyright (c) 2022-2022 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 "foundation/log.h"
22 #include <foundation/osal/thread/scoped_lock.h>
23 #include "foundation/utils/hitrace_utils.h"
24 #include "securec.h"
25 
26 #ifndef CA_DIR
27 #define CA_DIR "/etc/ssl/certs/"
28 #endif
29 
30 namespace OHOS {
31 namespace Media {
32 namespace Plugin {
33 namespace HttpPlugin {
HttpCurlClient(RxHeader headCallback,RxBody bodyCallback,void * userParam)34 HttpCurlClient::HttpCurlClient(RxHeader headCallback, RxBody bodyCallback, void *userParam)
35     : rxHeader_(headCallback), rxBody_(bodyCallback), userParam_(userParam)
36 {
37     MEDIA_LOG_I("HttpCurlClient ctor");
38 }
39 
~HttpCurlClient()40 HttpCurlClient::~HttpCurlClient()
41 {
42     MEDIA_LOG_I("~HttpCurlClient dtor");
43 }
44 
Init()45 Status HttpCurlClient::Init()
46 {
47     FALSE_LOG(curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK);
48     return Status::OK;
49 }
50 
Open(const std::string & url)51 Status HttpCurlClient::Open(const std::string& url)
52 {
53     easyHandle_ = curl_easy_init();
54     FALSE_RETURN_V(easyHandle_ != nullptr, Status::ERROR_NULL_POINTER);
55     InitCurlEnvironment(url);
56     return Status::OK;
57 }
58 
Close()59 Status HttpCurlClient::Close()
60 {
61     OSAL::ScopedLock lock(mutex_);
62     MEDIA_LOG_I("Close client");
63     curl_easy_setopt(easyHandle_, CURLOPT_TIMEOUT, 1);
64     if (easyHandle_) {
65         curl_easy_cleanup(easyHandle_);
66         easyHandle_ = nullptr;
67     }
68     return Status::OK;
69 }
70 
Deinit()71 Status HttpCurlClient::Deinit()
72 {
73     if (easyHandle_) {
74         curl_easy_cleanup(easyHandle_);
75         easyHandle_ = nullptr;
76     }
77     curl_global_cleanup();
78     return Status::OK;
79 }
80 
InitCurlEnvironment(const std::string & url)81 void HttpCurlClient::InitCurlEnvironment(const std::string& url)
82 {
83     curl_easy_setopt(easyHandle_, CURLOPT_URL, UrlParse(url).c_str());
84     curl_easy_setopt(easyHandle_, CURLOPT_CONNECTTIMEOUT, 5); // 5
85 
86     curl_easy_setopt(easyHandle_, CURLOPT_SSL_VERIFYPEER, 0L);
87     curl_easy_setopt(easyHandle_, CURLOPT_SSL_VERIFYHOST, 0L);
88     curl_easy_setopt(easyHandle_, CURLOPT_CAINFO, CA_DIR "cacert.pem");
89 
90     curl_easy_setopt(easyHandle_, CURLOPT_HTTPGET, 1L);
91 
92     curl_easy_setopt(easyHandle_, CURLOPT_FORBID_REUSE, 0L);
93     curl_easy_setopt(easyHandle_, CURLOPT_FOLLOWLOCATION, 1L);
94 
95     curl_easy_setopt(easyHandle_, CURLOPT_WRITEFUNCTION, rxBody_);
96     curl_easy_setopt(easyHandle_, CURLOPT_WRITEDATA, userParam_);
97     curl_easy_setopt(easyHandle_, CURLOPT_HEADERFUNCTION, rxHeader_);
98     curl_easy_setopt(easyHandle_, CURLOPT_HEADERDATA, userParam_);
99 
100     curl_easy_setopt(easyHandle_, CURLOPT_LOW_SPEED_LIMIT, 2); // 2
101     curl_easy_setopt(easyHandle_, CURLOPT_LOW_SPEED_TIME, 5); // 连续5s下载速度低于2kb/s会触发timeout
102 
103     curl_easy_setopt(easyHandle_, CURLOPT_TCP_KEEPALIVE, 1L);
104     curl_easy_setopt(easyHandle_, CURLOPT_TCP_KEEPINTVL, 5L); // 5 心跳
105 }
106 
UrlParse(const std::string & url)107 std::string HttpCurlClient::UrlParse(const std::string& url)
108 {
109     std::string s;
110     std::regex_replace(std::back_inserter(s), url.begin(), url.end(), std::regex(" "), "%20");
111     return s;
112 }
113 
114 // RequestData run in HttpDownload thread,
115 // Open, Close, Deinit run in other thread.
116 // Should call Open before start HttpDownload thread.
117 // Should Pause HttpDownload thread then Close, Deinit.
RequestData(long startPos,int len,NetworkServerErrorCode & serverCode,NetworkClientErrorCode & clientCode)118 Status HttpCurlClient::RequestData(long startPos, int len, NetworkServerErrorCode& serverCode,
119                                    NetworkClientErrorCode& clientCode)
120 {
121     SYNC_TRACER();
122     FALSE_RETURN_V(easyHandle_ != nullptr, Status::ERROR_NULL_POINTER);
123     if (startPos >= 0) {
124         char requestRange[128] = {0};
125         if (len > 0) {
126             snprintf_s(requestRange, sizeof(requestRange), sizeof(requestRange) - 1, "%ld-%ld",
127                        startPos, startPos + len - 1);
128         } else {
129             snprintf_s(requestRange, sizeof(requestRange), sizeof(requestRange) - 1, "%ld-", startPos);
130         }
131         MEDIA_LOG_DD("RequestData: requestRange " PUBLIC_LOG_S, requestRange);
132         curl_easy_setopt(easyHandle_, CURLOPT_RANGE, requestRange);
133     }
134     curl_slist *headers {nullptr};
135     headers = curl_slist_append(headers, "Connection: Keep-alive");
136     headers = curl_slist_append(headers, "Keep-Alive: timeout=120");
137     curl_easy_setopt(easyHandle_, CURLOPT_HTTPHEADER, headers);
138 
139     MEDIA_LOG_D("RequestData: startPos " PUBLIC_LOG_D32 ", len " PUBLIC_LOG_D32, static_cast<int>(startPos), len);
140     OSAL::ScopedLock lock(mutex_);
141     FALSE_RETURN_V(easyHandle_ != nullptr, Status::ERROR_NULL_POINTER);
142     CURLcode returnCode = curl_easy_perform(easyHandle_);
143     if (headers != nullptr) {
144         curl_slist_free_all(headers);
145     }
146     clientCode = NetworkClientErrorCode::ERROR_OK;
147     serverCode = 0;
148     if (returnCode != CURLE_OK) {
149         MEDIA_LOG_E("Curl error " PUBLIC_LOG_D32, returnCode);
150         if (returnCode == CURLE_WRITE_ERROR) {
151             clientCode = NetworkClientErrorCode::ERROR_NOT_RETRY;
152         } else if (returnCode == CURLE_COULDNT_CONNECT || returnCode == CURLE_OPERATION_TIMEDOUT) {
153             clientCode = NetworkClientErrorCode::ERROR_TIME_OUT;
154         } else {
155             clientCode = NetworkClientErrorCode::ERROR_UNKNOWN;
156         }
157         return Status::ERROR_CLIENT;
158     } else {
159         int64_t httpCode = 0;
160         curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &httpCode);
161         if (httpCode >= 400) { // 400
162             MEDIA_LOG_E("Http error " PUBLIC_LOG_D64, httpCode);
163             serverCode = httpCode;
164             return Status::ERROR_SERVER;
165         }
166     }
167     return Status::OK;
168 }
169 }
170 }
171 }
172 }