1 /*
2 * Copyright (c) 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
16 #include "upload/curl_adp.h"
17
18 #include <fcntl.h>
19 #include <unistd.h>
20
21 #include <cinttypes>
22 #include <climits>
23 #include <cstdio>
24 #include <fstream>
25 #include <ios>
26 #include <string>
27 #include <vector>
28
29 #include "common_timer_errors.h"
30 #include "constant.h"
31 #include "upload/upload_hilog_wrapper.h"
32 #include "upload/upload_task.h"
33
34 namespace OHOS::Request::Upload {
35 static constexpr const char *HTTP_DEFAULT_CA_PATH = "/etc/ssl/certs/cacert.pem";
CUrlAdp(std::vector<FileData> & fileDatas,std::shared_ptr<UploadConfig> & config)36 CUrlAdp::CUrlAdp(std::vector<FileData> &fileDatas, std::shared_ptr<UploadConfig> &config)
37 : timerId_(0), fileDatas_(fileDatas), config_(config), isCurlGlobalInit_(false), curlMulti_(nullptr),
38 isReadAbort_(false), timer_("uploadTimer")
39 {
40 }
41
~CUrlAdp()42 CUrlAdp::~CUrlAdp()
43 {
44 UPLOAD_HILOGI(UPLOAD_MODULE_FRAMEWORK, "~CUrlAdp()");
45 }
46
DoUpload(std::shared_ptr<IUploadTask> task)47 uint32_t CUrlAdp::DoUpload(std::shared_ptr<IUploadTask> task)
48 {
49 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK, "upload start");
50 if (task != nullptr) {
51 uploadTask_ = task;
52 }
53
54 uint32_t successCount = 0;
55 for (auto &vmem : fileDatas_) {
56 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK, "read abort stat: %{public}d file index: %{public}u", IsReadAbort(),
57 vmem.fileIndex);
58 if (IsReadAbort()) {
59 vmem.result = UPLOAD_TASK_REMOVED;
60 continue;
61 }
62
63 mfileData_ = vmem;
64 mfileData_.adp = shared_from_this();
65 vmem.result = static_cast<uint32_t>(UploadOneFile());
66 if (vmem.result == UPLOAD_OK) {
67 successCount++;
68 }
69 mfileData_.responseHead.clear();
70 if (mfileData_.list) {
71 curl_slist_free_all(mfileData_.list);
72 mfileData_.list = nullptr;
73 }
74 ClearCurlResource();
75 usleep(FILE_UPLOAD_INTERVAL);
76 }
77 mfileData_.adp = nullptr;
78 uploadTask_ = nullptr;
79 return (IsSuccess(successCount, fileDatas_.size())) ? UPLOAD_OK : UPLOAD_ERRORCODE_UPLOAD_FAIL;
80 }
81
IsSuccess(const uint32_t count,const uint32_t size)82 bool CUrlAdp::IsSuccess(const uint32_t count, const uint32_t size)
83 {
84 return (count == size);
85 }
86
MultiAddHandle(CURLM * curlMulti,std::vector<CURL * > & curlArray)87 bool CUrlAdp::MultiAddHandle(CURLM *curlMulti, std::vector<CURL *> &curlArray)
88 {
89 CURL *curl = curl_easy_init();
90 if (curl == nullptr) {
91 return false;
92 }
93
94 SetCurlOpt(curl);
95 curlArray.push_back(curl);
96 curl_multi_add_handle(curlMulti, curl);
97 return true;
98 }
99
SetHeadData(CURL * curl)100 void CUrlAdp::SetHeadData(CURL *curl)
101 {
102 std::vector<std::string> vec;
103 std::for_each(
104 config_->header.begin(), config_->header.end(), [&vec](const std::pair<std::string, std::string> &header) {
105 vec.emplace_back(header.first + ":" + header.second);
106 });
107 bool hasContentType = false;
108 for (auto &headerData : vec) {
109 if (headerData.find("Content-Type:") != std::string::npos) {
110 hasContentType = true;
111 }
112 mfileData_.list = curl_slist_append(mfileData_.list, headerData.c_str());
113 }
114
115 if (!hasContentType) {
116 std::string str = config_->method == PUT ? "Content-Type:application/octet-stream"
117 : "Content-Type:multipart/form-data";
118 mfileData_.list = curl_slist_append(mfileData_.list, str.c_str());
119 }
120 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, mfileData_.list);
121 }
122
SetBehaviorOpt(CURL * curl)123 void CUrlAdp::SetBehaviorOpt(CURL *curl)
124 {
125 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
126 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
127 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
128 }
129
SetCallbackOpt(CURL * curl)130 void CUrlAdp::SetCallbackOpt(CURL *curl)
131 {
132 curl_easy_setopt(curl, CURLOPT_HEADERDATA, &mfileData_);
133 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback);
134 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback);
135 curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &mfileData_);
136 }
137
SetNetworkOpt(CURL * curl)138 void CUrlAdp::SetNetworkOpt(CURL *curl)
139 {
140 curl_easy_setopt(curl, CURLOPT_URL, config_->url.c_str());
141 }
142
SetConnectionOpt(CURL * curl)143 void CUrlAdp::SetConnectionOpt(CURL *curl)
144 {
145 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
146 }
147
SetSslOpt(CURL * curl)148 void CUrlAdp::SetSslOpt(CURL *curl)
149 {
150 if (config_->url.find("https") != 0) {
151 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
152 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
153 return;
154 }
155 std::string certInfo = ReadCertification();
156 if (certInfo.empty()) {
157 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "Read certinfo failed");
158 return;
159 }
160 struct curl_blob blob {
161 .data = const_cast<char *>(certInfo.c_str()), .len = certInfo.size(), .flags = CURL_BLOB_COPY
162 };
163 std::string version = "CURL_SSLVERSION_TLSv1_2";
164 if (config_->header.find(tlsVersion) != config_->header.end()) {
165 version = config_->header[tlsVersion];
166 }
167 curl_easy_setopt(curl, CURLOPT_SSLVERSION, version.c_str());
168 CURLcode code = curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob);
169 if (code != CURLE_OK) {
170 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "set ssl ca option failed");
171 }
172 }
173
ReadCertification()174 std::string CUrlAdp::ReadCertification()
175 {
176 std::ifstream inFile(HTTP_DEFAULT_CA_PATH, std::ios::in | std::ios::binary);
177 if (!inFile.is_open()) {
178 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "open cacert.pem failed, errno:%{public}d", errno);
179 return "";
180 }
181 std::stringstream buf;
182 buf << inFile.rdbuf();
183 return std::string(buf.str());
184 }
185
SetCurlOpt(CURL * curl)186 void CUrlAdp::SetCurlOpt(CURL *curl)
187 {
188 SetHeadData(curl);
189 SetNetworkOpt(curl);
190 SetConnectionOpt(curl);
191 SetSslOpt(curl);
192 SetBehaviorOpt(curl);
193 SetCallbackOpt(curl);
194 if (config_->method == PUT) {
195 SetHttpPut(curl);
196 } else {
197 SetMimePost(curl);
198 }
199 }
200
SetMimePost(CURL * curl)201 void CUrlAdp::SetMimePost(CURL *curl)
202 {
203 curl_mimepart *part;
204 curl_mime *mime = curl_mime_init(curl);
205 if (!config_->data.empty()) {
206 for (auto &item : config_->data) {
207 part = curl_mime_addpart(mime);
208 curl_mime_name(part, item.name.c_str());
209 curl_mime_data(part, item.value.c_str(), item.value.size());
210 }
211 }
212 part = curl_mime_addpart(mime);
213 curl_mime_name(part, "file");
214 curl_mime_type(part, mfileData_.type.c_str());
215 curl_mime_filename(part, mfileData_.filename.c_str());
216 curl_mime_data_cb(part, mfileData_.totalsize, ReadCallback, NULL, NULL, &mfileData_);
217 curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
218 }
219
SetHttpPut(CURL * curl)220 void CUrlAdp::SetHttpPut(CURL *curl)
221 {
222 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
223 curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadCallback);
224 curl_easy_setopt(curl, CURLOPT_READDATA, &mfileData_);
225 curl_easy_setopt(curl, CURLOPT_INFILESIZE, mfileData_.totalsize);
226 }
227
UploadOneFile()228 int32_t CUrlAdp::UploadOneFile()
229 {
230 CurlGlobalInit();
231 curlMulti_ = curl_multi_init();
232 if (curlMulti_ == nullptr) {
233 CurlGlobalCleanup();
234 return UPLOAD_ERRORCODE_UPLOAD_LIB_ERROR;
235 }
236
237 bool ret = MultiAddHandle(curlMulti_, curlArray_);
238 if (ret == false) {
239 return UPLOAD_ERRORCODE_UPLOAD_LIB_ERROR;
240 }
241
242 int isRunning = 0;
243 curl_multi_perform(curlMulti_, &isRunning);
244 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK, "isRunning = %{public}d", isRunning);
245 do {
246 int numfds = 0;
247 int res = curl_multi_wait(curlMulti_, NULL, 0, TRANS_TIMEOUT_MS, &numfds);
248 if (res != CURLM_OK) {
249 return res;
250 }
251 curl_multi_perform(curlMulti_, &isRunning);
252 } while (isRunning);
253
254 return CheckUploadStatus(curlMulti_);
255 }
256
CurlGlobalInit()257 void CUrlAdp::CurlGlobalInit()
258 {
259 std::lock_guard<std::mutex> guard(globalMutex_);
260 if (!isCurlGlobalInit_) {
261 isCurlGlobalInit_ = true;
262 }
263 }
264
CurlGlobalCleanup()265 void CUrlAdp::CurlGlobalCleanup()
266 {
267 std::lock_guard<std::mutex> guard(globalMutex_);
268 if (isCurlGlobalInit_) {
269 isCurlGlobalInit_ = false;
270 }
271 }
272
CheckUploadStatus(CURLM * curlMulti)273 int CUrlAdp::CheckUploadStatus(CURLM *curlMulti)
274 {
275 int msgsLeft = 0;
276 int returnCode = UPLOAD_ERRORCODE_UPLOAD_FAIL;
277 CURLMsg *msg = NULL;
278 if (IsReadAbort()) {
279 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "CheckUploadStatus IsReadAbort is %{public}d", IsReadAbort());
280 return returnCode;
281 }
282 while ((msg = curl_multi_info_read(curlMulti, &msgsLeft))) {
283 if (msg->msg != CURLMSG_DONE) {
284 continue;
285 }
286 CURL *eh = NULL;
287 eh = msg->easy_handle;
288 returnCode = msg->data.result;
289 if (returnCode == CURLE_SSL_CONNECT_ERROR) {
290 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "upload fail curl error %{public}d", returnCode);
291 return UPLOAD_CURLE_SSL_CONNECT_ERROR;
292 }
293 if (returnCode != CURLE_OK) {
294 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "upload fail curl error %{public}d", returnCode);
295 return UPLOAD_ERRORCODE_UPLOAD_LIB_ERROR;
296 }
297
298 long respCode = 0;
299 curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &respCode);
300 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK, "upload http code %{public}ld", respCode);
301 if (respCode != HTTP_SUCCESS) {
302 returnCode = respCode;
303 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "upload fail http error %{public}d", returnCode);
304 return UPLOAD_ERRORCODE_UPLOAD_FAIL;
305 }
306 returnCode = UPLOAD_OK;
307 }
308 return returnCode;
309 }
310
Remove()311 bool CUrlAdp::Remove()
312 {
313 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK, "remove");
314 std::lock_guard<std::mutex> guard(curlMutex_);
315 isReadAbort_ = true;
316 return true;
317 }
318
ClearCurlResource()319 bool CUrlAdp::ClearCurlResource()
320 {
321 std::lock_guard<std::mutex> guard(mutex_);
322 for (auto url : curlArray_) {
323 curl_multi_remove_handle(curlMulti_, url);
324 curl_easy_cleanup(url);
325 }
326 curlArray_.clear();
327 if (curlMulti_) {
328 curl_multi_cleanup(curlMulti_);
329 curlMulti_ = nullptr;
330 }
331 CurlGlobalCleanup();
332 return true;
333 }
334
CheckCUrlAdp(FileData * fData)335 bool CUrlAdp::CheckCUrlAdp(FileData *fData)
336 {
337 if (fData == nullptr || fData->adp == nullptr) {
338 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "CheckCUrlAdp url == nullptr");
339 return false;
340 }
341 std::lock_guard<std::mutex> lock(fData->adp->curlMutex_);
342 if (fData->adp->IsReadAbort()) {
343 UPLOAD_HILOGE(UPLOAD_MODULE_FRAMEWORK, "CheckCUrlAdp url->IsReadAbort()");
344 return false;
345 }
346 return true;
347 }
348
ProgressCallback(void * clientp,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)349 int CUrlAdp::ProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
350 {
351 FileData *fData = static_cast<FileData *>(clientp);
352 if (!CheckCUrlAdp(fData)) {
353 return UPLOAD_ERRORCODE_UPLOAD_FAIL;
354 }
355
356 std::shared_ptr<CUrlAdp> url = fData->adp;
357 std::lock_guard<std::mutex> lock(url->curlMutex_);
358 if (ulnow > 0) {
359 fData->upsize = fData->totalsize - (ultotal - ulnow);
360 } else {
361 fData->upsize = ulnow;
362 }
363
364 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK,
365 "progress upload total: %{public}" PRIu64 " upload now: %{public}" PRIu64 " upload size: %{public}" PRIu64
366 " total size: %{public}" PRIu64 " thread:%{public}lu",
367 ultotal, ulnow, fData->upsize, fData->totalsize, pthread_self());
368
369 if (url->uploadTask_) {
370 int64_t totalulnow = 0;
371 for (auto &vmem : url->fileDatas_) {
372 if (fData->filename == vmem.filename) {
373 vmem.upsize = fData->upsize;
374 }
375 totalulnow += vmem.upsize;
376 }
377 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK,
378 "report progress total upload size: %{public}" PRIu64 " upload now: %{public}" PRIu64, totalulnow, ultotal);
379 }
380 return 0;
381 }
382
HeaderCallback(char * buffer,size_t size,size_t nitems,void * userdata)383 size_t CUrlAdp::HeaderCallback(char *buffer, size_t size, size_t nitems, void *userdata)
384 {
385 FileData *fData = static_cast<FileData *>(userdata);
386 if (!CheckCUrlAdp(fData)) {
387 return CURLE_WRITE_ERROR;
388 }
389
390 std::shared_ptr<CUrlAdp> url = fData->adp;
391 std::lock_guard<std::mutex> lock(url->curlMutex_);
392 std::string stmp(buffer, size * nitems);
393 url->SplitHttpMessage(stmp, fData);
394
395 if (url->uploadTask_ && fData->headSendFlag == COLLECT_END_FLAG) {
396 std::string headers = std::accumulate(fData->responseHead.begin(), fData->responseHead.end(), std::string(""));
397 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK, "report head len: %{public}zu, content: %{public}s", headers.length(),
398 headers.c_str());
399 NotifyAPI5(fData, headers);
400 fData->responseHead.clear();
401 fData->httpCode = 0;
402 }
403 return size * nitems;
404 }
405
SplitHttpMessage(const std::string & stmp,FileData * & fData)406 void CUrlAdp::SplitHttpMessage(const std::string &stmp, FileData *&fData)
407 {
408 const std::string headEndFlag = "\r\n";
409 if (std::string::npos != stmp.find("HTTP")) {
410 fData->headSendFlag = COLLECT_DO_FLAG;
411 const int codeLen = 3;
412 std::string::size_type position = stmp.find_first_of(" ");
413 std::string scode(stmp, position + 1, codeLen);
414 fData->httpCode = std::stol(scode);
415 } else if (stmp == headEndFlag) {
416 fData->headSendFlag = COLLECT_END_FLAG;
417 }
418 if (fData->headSendFlag == COLLECT_DO_FLAG || fData->headSendFlag == COLLECT_END_FLAG) {
419 fData->responseHead.push_back(stmp);
420 }
421 }
422
NotifyAPI5(FileData * fData,std::string & headers)423 void CUrlAdp::NotifyAPI5(FileData *fData, std::string &headers)
424 {
425 if (fData->httpCode == HTTP_SUCCESS) {
426 if (fData->adp->fileDatas_.size() == fData->fileIndex && fData->adp->config_->fsuccess != nullptr) {
427 UploadResponse resData;
428 resData.headers = headers;
429 resData.code = fData->httpCode;
430 fData->adp->config_->fsuccess(resData);
431 }
432 } else {
433 if (fData->adp->config_->ffail) {
434 fData->adp->config_->ffail(headers, fData->httpCode);
435 }
436 }
437 }
438
ReadCallback(char * buffer,size_t size,size_t nitems,void * arg)439 size_t CUrlAdp::ReadCallback(char *buffer, size_t size, size_t nitems, void *arg)
440 {
441 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK, "size is %{public}zu, nitems is %{public}zu.", size, nitems);
442 FileData *fData = static_cast<FileData *>(arg);
443 if (!CheckCUrlAdp(fData) || ferror(fData->fp)) {
444 return CURL_READFUNC_ABORT;
445 }
446
447 std::shared_ptr<CUrlAdp> url = fData->adp;
448 std::lock_guard<std::mutex> lock(url->curlMutex_);
449 url->StartTimer();
450 size_t readSize = fread(buffer, size, nitems, fData->fp);
451 url->StopTimer();
452
453 return readSize;
454 }
455
StartTimer()456 void CUrlAdp::StartTimer()
457 {
458 uint32_t ret = timer_.Setup();
459 if (ret != Utils::TIMER_ERR_OK) {
460 UPLOAD_HILOGI(UPLOAD_MODULE_FRAMEWORK, "Create Timer error");
461 return;
462 }
463 auto TimeOutCallback = [this]() {
464 UPLOAD_HILOGD(UPLOAD_MODULE_FRAMEWORK, "OutTime error");
465 this->isReadAbort_ = true;
466 };
467 timerId_ = timer_.Register(TimeOutCallback, READFILE_TIMEOUT_MS, true);
468 }
469
StopTimer()470 void CUrlAdp::StopTimer()
471 {
472 timer_.Unregister(timerId_);
473 timer_.Shutdown();
474 }
475 } // namespace OHOS::Request::Upload