1 /*
2  * Copyright (c) 2021 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 "adapter/preview/osal/fetch_manager.h"
17 
18 #include <memory>
19 #include <mutex>
20 
21 #include "curl/curl.h"
22 
23 #include "adapter/preview/osal/http_constant.h"
24 #include "base/log/log.h"
25 #include "base/utils/singleton.h"
26 
27 #define ACE_CURL_EASY_SET_OPTION(handle, opt, data)                                                 \
28     do {                                                                                            \
29         CURLcode result = curl_easy_setopt(handle, opt, data);                                      \
30         if (result != CURLE_OK) {                                                                   \
31             LOGW("Failed to set option: %{public}s, %{public}s", #opt, curl_easy_strerror(result)); \
32             return false;                                                                           \
33         }                                                                                           \
34     } while (0)
35 
36 namespace OHOS::Ace {
37 namespace {
38 
39 class FetchManagerImpl final : public FetchManager, public Singleton<FetchManagerImpl> {
40     DECLARE_SINGLETON(FetchManagerImpl);
41     ACE_DISALLOW_MOVE(FetchManagerImpl);
42 
43 public:
Fetch(const RequestData requestData,const int32_t callbackId,ResponseData & responseData)44     bool Fetch(const RequestData requestData, const int32_t callbackId, ResponseData& responseData) override
45     {
46         if (!Initialize()) {
47             return false;
48         }
49 
50         std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), &curl_easy_cleanup);
51         if (!handle) {
52             return false;
53         }
54 
55         struct curl_slist* header = nullptr;
56         if (!requestData.GetHeader().empty()) {
57             for (auto&& [key, value] : requestData.GetHeader()) {
58                 header = curl_slist_append(header, (key + ":" + value).c_str());
59             }
60             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HTTPHEADER, header);
61         }
62 
63         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_TIMEOUT_MS, HttpConstant::TIME_OUT);
64         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_CONNECTTIMEOUT_MS, HttpConstant::TIME_OUT);
65         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_BUFFERSIZE, HttpConstant::BUFFER_SIZE);
66 
67         std::string responseBody;
68         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEFUNCTION, OnWritingMemoryBody);
69         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEDATA, &responseBody);
70 
71         std::string responseHeader;
72         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader);
73         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HEADERDATA, &responseHeader);
74 
75         // Some servers don't like requests that are made without a user-agent field, so we provide one
76         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0");
77 
78         std::string method = requestData.GetMethod();
79         if (method.empty()) {
80             method = "GET";
81         }
82         if (method == HttpConstant::HTTP_METHOD_HEAD || method == HttpConstant::HTTP_METHOD_OPTIONS ||
83             method == HttpConstant::HTTP_METHOD_DELETE || method == HttpConstant::HTTP_METHOD_TRACE ||
84             method == HttpConstant::HTTP_METHOD_GET) {
85             SetOptionForGet(requestData, handle.get());
86         } else if (method == HttpConstant::HTTP_METHOD_POST || method == HttpConstant::HTTP_METHOD_PUT) {
87             SetOptionForPost(requestData, handle.get());
88         } else {
89             responseData.SetCode(HttpConstant::ERROR);
90             responseData.SetData(responseBody);
91             return true;
92         }
93 
94         CURLcode result = curl_easy_perform(handle.get());
95         if (result != CURLE_OK) {
96             LOGW("Failed to fetch, url: %{private}s, %{public}s", requestData.GetUrl().c_str(),
97                 curl_easy_strerror(result));
98             responseData.SetCode(HttpConstant::ERROR);
99             responseData.SetData(responseBody);
100             return true;
101         }
102 
103         char* ct = nullptr;
104         curl_easy_getinfo(handle.get(), CURLINFO_CONTENT_TYPE, &ct);
105 
106         int32_t responseCode = HttpConstant::ERROR;
107         curl_easy_getinfo(handle.get(), CURLINFO_RESPONSE_CODE, &responseCode);
108         responseData.SetCode(responseCode);
109         responseData.SetData(responseBody);
110         responseData.SetHeaders(responseHeader);
111         if (header != nullptr) {
112             curl_slist_free_all(header);
113         }
114         return true;
115     }
116 
SetOptionForGet(const RequestData requestData,CURL * curl) const117     bool SetOptionForGet(const RequestData requestData, CURL* curl) const
118     {
119         // refer to function buildConnectionWithParam() in HttpFetchImpl.java
120         std::string url = requestData.GetUrl();
121         if (requestData.GetData() != "") {
122             std::size_t index = url.find(HttpConstant::URL_PARAM_SEPARATOR);
123             if (index != std::string::npos) {
124                 std::string param = url.substr(index + 1);
125 
126                 std::string encodeIn = param + HttpConstant::URL_PARAM_DELIMITER + requestData.GetData();
127                 char* encodeOut = curl_easy_escape(curl, encodeIn.c_str(), 0);
128                 if (encodeOut != nullptr) {
129                     url = url.substr(0, index + 1) + encodeOut;
130                     curl_free(encodeOut);
131                 }
132             } else {
133                 char* encodeOut = curl_easy_escape(curl, requestData.GetData().c_str(), 0);
134                 if (encodeOut != nullptr) {
135                     url = url + HttpConstant::URL_PARAM_SEPARATOR + encodeOut;
136                     curl_free(encodeOut);
137                 }
138             }
139         }
140         ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, url.c_str());
141         return true;
142     }
143 
SetOptionForPost(const RequestData requestData,CURL * curl) const144     bool SetOptionForPost(const RequestData requestData, CURL* curl) const
145     {
146         // refer to function buildConnectionWithStream() in HttpFetchImpl.java
147         std::string url = requestData.GetUrl();
148         ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, url.c_str());
149         ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDS, requestData.GetData().c_str());
150         return true;
151     }
152 
153 private:
OnWritingMemoryBody(const void * data,size_t size,size_t memBytes,void * userData)154     static size_t OnWritingMemoryBody(const void* data, size_t size, size_t memBytes, void* userData)
155     {
156         ((std::string*)userData)->append((char*)data, 0, size * memBytes);
157         return size * memBytes;
158     }
OnWritingMemoryHeader(const void * data,size_t size,size_t memBytes,void * userData)159     static size_t OnWritingMemoryHeader(const void* data, size_t size, size_t memBytes, void* userData)
160     {
161         ((std::string*)userData)->append((char*)data, 0, size * memBytes);
162         return size * memBytes;
163     }
164 
Initialize()165     bool Initialize()
166     {
167         if (initialized_) {
168             return true;
169         }
170 
171         std::lock_guard<std::mutex> lock(mutex_);
172         if (initialized_) {
173             return true;
174         }
175         if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
176             LOGE("Failed to initialize 'curl'");
177             return false;
178         }
179         initialized_ = true;
180         return true;
181     }
182 
183     std::mutex mutex_;
184     bool initialized_ = false;
185 };
186 
187 FetchManagerImpl::FetchManagerImpl() = default;
188 
~FetchManagerImpl()189 FetchManagerImpl::~FetchManagerImpl()
190 {
191     curl_global_cleanup();
192 }
193 
194 } // namespace
195 
GetInstance()196 FetchManager& FetchManager::GetInstance()
197 {
198     return Singleton<FetchManagerImpl>::GetInstance();
199 }
200 
201 } // namespace OHOS::Ace
202