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