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 LOGE("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 LOGE("Failed to create fetch task");
53 return false;
54 }
55
56 struct curl_slist* header = nullptr;
57 if (!requestData.GetHeader().empty()) {
58 for (auto&& [key, value] : requestData.GetHeader()) {
59 header = curl_slist_append(header, (key + ":" + value).c_str());
60 }
61 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HTTPHEADER, header);
62 }
63
64 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_TIMEOUT_MS, HttpConstant::TIME_OUT);
65 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_CONNECTTIMEOUT_MS, HttpConstant::TIME_OUT);
66 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_BUFFERSIZE, HttpConstant::BUFFER_SIZE);
67
68 std::string responseBody;
69 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEFUNCTION, OnWritingMemoryBody);
70 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEDATA, &responseBody);
71
72 std::string responseHeader;
73 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader);
74 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HEADERDATA, &responseHeader);
75
76 // Some servers don't like requests that are made without a user-agent field, so we provide one
77 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0");
78
79 std::string method = requestData.GetMethod();
80 if (method.empty()) {
81 method = "GET";
82 }
83 if (method == HttpConstant::HTTP_METHOD_HEAD || method == HttpConstant::HTTP_METHOD_OPTIONS ||
84 method == HttpConstant::HTTP_METHOD_DELETE || method == HttpConstant::HTTP_METHOD_TRACE ||
85 method == HttpConstant::HTTP_METHOD_GET) {
86 SetOptionForGet(requestData, handle.get());
87 } else if (method == HttpConstant::HTTP_METHOD_POST || method == HttpConstant::HTTP_METHOD_PUT) {
88 SetOptionForPost(requestData, handle.get());
89 } else {
90 LOGE("no method match!");
91 responseData.SetCode(HttpConstant::ERROR);
92 responseData.SetData(responseBody);
93 return true;
94 }
95
96 CURLcode result = curl_easy_perform(handle.get());
97 if (result != CURLE_OK) {
98 LOGE("Failed to fetch, url: %{private}s, %{public}s", requestData.GetUrl().c_str(),
99 curl_easy_strerror(result));
100 responseData.SetCode(HttpConstant::ERROR);
101 responseData.SetData(responseBody);
102 return true;
103 }
104
105 char* ct = nullptr;
106 CURLcode res = curl_easy_getinfo(handle.get(), CURLINFO_CONTENT_TYPE, &ct);
107 if ((CURLE_OK == res) && ct) {
108 LOGD("fetch-preview content_type: %{public}s", ct);
109 }
110
111 int32_t responseCode = HttpConstant::ERROR;
112 curl_easy_getinfo(handle.get(), CURLINFO_RESPONSE_CODE, &responseCode);
113 responseData.SetCode(responseCode);
114 responseData.SetData(responseBody);
115 responseData.SetHeaders(responseHeader);
116
117 curl_slist_free_all(header);
118
119 return true;
120 }
121
SetOptionForGet(const RequestData requestData,CURL * curl) const122 bool SetOptionForGet(const RequestData requestData, CURL* curl) const
123 {
124 // refer to function buildConnectionWithParam() in HttpFetchImpl.java
125 LOGD("begin to set option for get and encode final url");
126 std::string url = requestData.GetUrl();
127 if (requestData.GetData() != "") {
128 std::size_t index = url.find(HttpConstant::URL_PARAM_SEPARATOR);
129 if (index != std::string::npos) {
130 std::string param = url.substr(index + 1);
131
132 std::string encodeIn = param + HttpConstant::URL_PARAM_DELIMITER + requestData.GetData();
133 char* encodeOut = curl_easy_escape(curl, encodeIn.c_str(), 0);
134 if (encodeOut != nullptr) {
135 url = url.substr(0, index + 1) + encodeOut;
136 curl_free(encodeOut);
137 }
138 } else {
139 char* encodeOut = curl_easy_escape(curl, requestData.GetData().c_str(), 0);
140 if (encodeOut != nullptr) {
141 url = url + HttpConstant::URL_PARAM_SEPARATOR + encodeOut;
142 curl_free(encodeOut);
143 }
144 }
145 }
146 LOGD("final url : %{public}s", url.c_str());
147 ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, url.c_str());
148 return true;
149 }
150
SetOptionForPost(const RequestData requestData,CURL * curl) const151 bool SetOptionForPost(const RequestData requestData, CURL* curl) const
152 {
153 // refer to function buildConnectionWithStream() in HttpFetchImpl.java
154 LOGD("begin to set option for post");
155 std::string url = requestData.GetUrl();
156 LOGD("final url : %{public}s", url.c_str());
157 ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, url.c_str());
158 ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDS, requestData.GetData().c_str());
159 return true;
160 }
161
162 private:
OnWritingMemoryBody(const void * data,size_t size,size_t memBytes,void * userData)163 static size_t OnWritingMemoryBody(const void* data, size_t size, size_t memBytes, void* userData)
164 {
165 ((std::string*)userData)->append((char*)data, 0, size * memBytes);
166 return size * memBytes;
167 }
OnWritingMemoryHeader(const void * data,size_t size,size_t memBytes,void * userData)168 static size_t OnWritingMemoryHeader(const void* data, size_t size, size_t memBytes, void* userData)
169 {
170 ((std::string*)userData)->append((char*)data, 0, size * memBytes);
171 return size * memBytes;
172 }
173
Initialize()174 bool Initialize()
175 {
176 if (initialized_) {
177 return true;
178 }
179
180 std::lock_guard<std::mutex> lock(mutex_);
181 if (initialized_) {
182 return true;
183 }
184 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
185 LOGE("Failed to initialize 'curl'");
186 return false;
187 }
188 initialized_ = true;
189 return true;
190 }
191
192 std::mutex mutex_;
193 bool initialized_ = false;
194 };
195
196 FetchManagerImpl::FetchManagerImpl() = default;
197
~FetchManagerImpl()198 FetchManagerImpl::~FetchManagerImpl()
199 {
200 curl_global_cleanup();
201 }
202
203 } // namespace
204
GetInstance()205 FetchManager& FetchManager::GetInstance()
206 {
207 return Singleton<FetchManagerImpl>::GetInstance();
208 }
209
210 } // namespace OHOS::Ace
211