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 "fetch_exec.h"
17
18 #include <algorithm>
19 #include <cstring>
20 #include <memory>
21
22 #include "constant.h"
23 #include "netstack_common_utils.h"
24 #include "netstack_log.h"
25 #include "napi_utils.h"
26 #include "securec.h"
27
28 #define NETSTACK_CURL_EASY_SET_OPTION(handle, opt, data, asyncContext) \
29 do { \
30 CURLcode result = curl_easy_setopt(handle, opt, data); \
31 if (result != CURLE_OK) { \
32 const char *err = curl_easy_strerror(result); \
33 NETSTACK_LOGE("Failed to set option: %{public}s, %{public}s %{public}d", #opt, err, result); \
34 (asyncContext)->SetErrorCode(result); \
35 return false; \
36 } \
37 } while (0)
38
39 #define NETSTACK_CURL_EASY_PERFORM(handle, asyncContext) \
40 do { \
41 CURLcode result = curl_easy_perform(handle); \
42 if (result != CURLE_OK) { \
43 NETSTACK_LOGE("request fail, err: %{public}s, result: %{public}d", \
44 curl_easy_strerror(result), result); \
45 (asyncContext)->SetErrorCode(result); \
46 return false; \
47 } \
48 } while (0)
49
50 #define NETSTACK_CURL_EASY_GET_INFO(handle, opt, data, asyncContext) \
51 do { \
52 CURLcode result = curl_easy_getinfo(handle, opt, data); \
53 if (result != CURLE_OK) { \
54 const char *err = curl_easy_strerror(result); \
55 NETSTACK_LOGE("Failed to get info: %{public}s, %{public}s %{public}d", #opt, err, result); \
56 (asyncContext)->SetErrorCode(result); \
57 return false; \
58 } \
59 } while (0)
60
61 static constexpr const int FAIL_CALLBACK_PARAM = 2;
62
63 namespace OHOS::NetStack::Fetch {
64 std::mutex FetchExec::mutex_;
65
66 bool FetchExec::initialized_ = false;
67
ExecFetch(FetchContext * context)68 bool FetchExec::ExecFetch(FetchContext *context)
69 {
70 if (!initialized_) {
71 NETSTACK_LOGE("curl not init");
72 return false;
73 }
74
75 std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), curl_easy_cleanup);
76
77 if (!handle) {
78 NETSTACK_LOGE("Failed to create fetch task");
79 return false;
80 }
81
82 std::vector<std::string> vec;
83 std::for_each(context->request.GetHeader().begin(), context->request.GetHeader().end(),
84 [&vec](const std::pair<std::string, std::string> &p) {
85 vec.emplace_back(p.first + FetchConstant::HTTP_HEADER_SEPARATOR + p.second);
86 });
87 std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)> header(MakeHeaders(vec), curl_slist_free_all);
88
89 if (!SetOption(handle.get(), context, header.get())) {
90 NETSTACK_LOGE("set option failed");
91 return false;
92 }
93
94 NETSTACK_CURL_EASY_PERFORM(handle.get(), context);
95
96 int32_t responseCode;
97 NETSTACK_CURL_EASY_GET_INFO(handle.get(), CURLINFO_RESPONSE_CODE, &responseCode, context);
98 NETSTACK_LOGI("Fetch responseCode is %{public}d", responseCode);
99
100 context->response.SetResponseCode(responseCode);
101 context->response.ParseHeaders();
102
103 return true;
104 }
105
FetchCallback(FetchContext * context)106 napi_value FetchExec::FetchCallback(FetchContext *context)
107 {
108 if (context->IsExecOK()) {
109 NETSTACK_LOGI("Fetch execute success");
110 napi_value success = context->GetSuccessCallback();
111 if (NapiUtils::GetValueType(context->GetEnv(), success) == napi_function) {
112 napi_value response = MakeResponse(context);
113 napi_value undefined = NapiUtils::GetUndefined(context->GetEnv());
114 napi_value argv[1] = {response};
115 (void)NapiUtils::CallFunction(context->GetEnv(), undefined, success, 1, argv);
116 }
117 } else {
118 NETSTACK_LOGI("Fetch execute failed");
119 napi_value fail = context->GetFailCallback();
120 if (NapiUtils::GetValueType(context->GetEnv(), fail) == napi_function) {
121 napi_value errData = NapiUtils::GetUndefined(context->GetEnv());
122 napi_value errCode = NapiUtils::CreateUint32(context->GetEnv(), context->GetErrorCode());
123 napi_value undefined = NapiUtils::GetUndefined(context->GetEnv());
124 napi_value argv[FAIL_CALLBACK_PARAM] = {errData, errCode};
125 (void)NapiUtils::CallFunction(context->GetEnv(), undefined, fail, FAIL_CALLBACK_PARAM, argv);
126 }
127 }
128
129 NETSTACK_LOGI("Fetch Call complete");
130 napi_value complete = context->GetCompleteCallback();
131 if (NapiUtils::GetValueType(context->GetEnv(), complete) == napi_function) {
132 napi_value undefined = NapiUtils::GetUndefined(context->GetEnv());
133 (void)NapiUtils::CallFunction(context->GetEnv(), undefined, complete, 0, nullptr);
134 }
135
136 return NapiUtils::GetUndefined(context->GetEnv());
137 }
138
MakeResponse(FetchContext * context)139 napi_value FetchExec::MakeResponse(FetchContext *context)
140 {
141 napi_value object = NapiUtils::CreateObject(context->GetEnv());
142 if (NapiUtils::GetValueType(context->GetEnv(), object) != napi_object) {
143 return nullptr;
144 }
145
146 NapiUtils::SetUint32Property(context->GetEnv(), object, FetchConstant::RESPONSE_KEY_CODE,
147 context->response.GetResponseCode());
148
149 napi_value header = MakeResponseHeader(context);
150 if (NapiUtils::GetValueType(context->GetEnv(), header) == napi_object) {
151 NapiUtils::SetNamedProperty(context->GetEnv(), object, FetchConstant::RESPONSE_KEY_HEADERS, header);
152 }
153
154 if (CommonUtils::ToLower(context->GetResponseType()) == FetchConstant::HTTP_RESPONSE_TYPE_JSON) {
155 napi_value data = NapiUtils::JsonParse(context->GetEnv(), context->response.GetData());
156 NapiUtils::SetNamedProperty(context->GetEnv(), object, FetchConstant::RESPONSE_KEY_DATA, data);
157 return object;
158 }
159
160 /* now just support utf8 */
161 NapiUtils::SetStringPropertyUtf8(context->GetEnv(), object, FetchConstant::RESPONSE_KEY_DATA,
162 context->response.GetData());
163 return object;
164 }
165
MakeUrl(const std::string & url,std::string param,const std::string & extraParam)166 std::string FetchExec::MakeUrl(const std::string &url, std::string param, const std::string &extraParam)
167 {
168 if (param.empty()) {
169 param += extraParam;
170 } else {
171 param += FetchConstant::HTTP_URL_PARAM_SEPARATOR;
172 param += extraParam;
173 }
174
175 if (param.empty()) {
176 return url;
177 }
178
179 return url + FetchConstant::HTTP_URL_PARAM_START + param;
180 }
181
MethodForGet(const std::string & method)182 bool FetchExec::MethodForGet(const std::string &method)
183 {
184 return (method == FetchConstant::HTTP_METHOD_HEAD || method == FetchConstant::HTTP_METHOD_OPTIONS ||
185 method == FetchConstant::HTTP_METHOD_DELETE || method == FetchConstant::HTTP_METHOD_TRACE ||
186 method == FetchConstant::HTTP_METHOD_GET || method == FetchConstant::HTTP_METHOD_CONNECT);
187 }
188
MethodForPost(const std::string & method)189 bool FetchExec::MethodForPost(const std::string &method)
190 {
191 return (method == FetchConstant::HTTP_METHOD_POST || method == FetchConstant::HTTP_METHOD_PUT);
192 }
193
EncodeUrlParam(std::string & str)194 bool FetchExec::EncodeUrlParam(std::string &str)
195 {
196 char encoded[4];
197 std::string encodeOut;
198 for (size_t i = 0; i < strlen(str.c_str()); ++i) {
199 auto c = static_cast<uint8_t>(str.c_str()[i]);
200 if (IsUnReserved(c)) {
201 encodeOut += static_cast<char>(c);
202 } else {
203 if (sprintf_s(encoded, sizeof(encoded), "%%%02X", c) < 0) {
204 return false;
205 }
206 encodeOut += encoded;
207 }
208 }
209
210 if (str == encodeOut) {
211 return false;
212 }
213 str = encodeOut;
214 return true;
215 }
216
Initialize()217 bool FetchExec::Initialize()
218 {
219 std::lock_guard<std::mutex> lock(mutex_);
220 if (initialized_) {
221 return true;
222 }
223 NETSTACK_LOGI("call curl_global_init");
224 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
225 NETSTACK_LOGE("Failed to initialize 'curl'");
226 return false;
227 }
228 initialized_ = true;
229 return initialized_;
230 }
231
SetOption(CURL * curl,FetchContext * context,struct curl_slist * requestHeader)232 bool FetchExec::SetOption(CURL *curl, FetchContext *context, struct curl_slist *requestHeader)
233 {
234 const std::string &method = context->request.GetMethod();
235 if (!MethodForGet(method) && !MethodForPost(method)) {
236 NETSTACK_LOGE("method %{public}s not supported", method.c_str());
237 return false;
238 }
239
240 if (context->request.GetMethod() == FetchConstant::HTTP_METHOD_HEAD) {
241 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOBODY, 1L, context);
242 }
243
244 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, context->request.GetUrl().c_str(), context);
245 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CUSTOMREQUEST, method.c_str(), context);
246
247 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEFUNCTION, OnWritingMemoryBody, context);
248 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEDATA, context, context);
249
250 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader, context);
251 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERDATA, context, context);
252
253 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTPHEADER, requestHeader, context);
254
255 // Some servers don't like requests that are made without a user-agent field, so we provide one
256 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_USERAGENT, FetchConstant::HTTP_DEFAULT_USER_AGENT, context);
257
258 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_FOLLOWLOCATION, 1L, context);
259
260 /* first #undef CURL_DISABLE_COOKIES in curl config */
261 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_COOKIEFILE, "", context);
262
263 #if NETSTACK_USE_PROXY
264 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXY, NETSTACK_PROXY_URL_PORT, context);
265 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYTYPE, NETSTACK_PROXY_TYPE, context);
266 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTPPROXYTUNNEL, 1L, context);
267 #ifdef NETSTACK_PROXY_PASS
268 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYUSERPWD, NETSTACK_PROXY_PASS, context);
269 #endif // NETSTACK_PROXY_PASS
270 #endif // NETSTACK_USE_PROXY
271
272 #if NO_SSL_CERTIFICATION
273 // in real life, you should buy a ssl certification and rename it to /etc/ssl/cert.pem
274 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 0L, context);
275 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 0L, context);
276 #else
277 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CAINFO, nullptr, context);
278 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CAPATH, FetchConstant::HTTP_PREPARE_CA_PATH, context);
279 #endif // NO_SSL_CERTIFICATION
280
281 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOPROGRESS, 1L, context);
282 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOSIGNAL, 1L, context);
283 #if HTTP_CURL_PRINT_VERBOSE
284 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_VERBOSE, 1L, context);
285 #endif
286
287 if (MethodForPost(method) && !context->request.GetBody().empty()) {
288 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POST, 1L, context);
289 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDS, context->request.GetBody().c_str(), context);
290 NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDSIZE, context->request.GetBody().size(), context);
291 }
292
293 return true;
294 }
295
OnWritingMemoryBody(const void * data,size_t size,size_t memBytes,void * userData)296 size_t FetchExec::OnWritingMemoryBody(const void *data, size_t size, size_t memBytes, void *userData)
297 {
298 auto context = static_cast<FetchContext *>(userData);
299 context->response.AppendData(data, size * memBytes);
300 return size * memBytes;
301 }
302
OnWritingMemoryHeader(const void * data,size_t size,size_t memBytes,void * userData)303 size_t FetchExec::OnWritingMemoryHeader(const void *data, size_t size, size_t memBytes, void *userData)
304 {
305 auto context = static_cast<FetchContext *>(userData);
306 context->response.AppendRawHeader(data, size * memBytes);
307 return size * memBytes;
308 }
309
MakeHeaders(const std::vector<std::string> & vec)310 struct curl_slist *FetchExec::MakeHeaders(const std::vector<std::string> &vec)
311 {
312 struct curl_slist *header = nullptr;
313 std::for_each(vec.begin(), vec.end(), [&header](const std::string &s) {
314 if (!s.empty()) {
315 header = curl_slist_append(header, s.c_str());
316 }
317 });
318 return header;
319 }
320
MakeResponseHeader(FetchContext * context)321 napi_value FetchExec::MakeResponseHeader(FetchContext *context)
322 {
323 napi_value header = NapiUtils::CreateObject(context->GetEnv());
324 if (NapiUtils::GetValueType(context->GetEnv(), header) == napi_object) {
325 std::for_each(context->response.GetHeader().begin(), context->response.GetHeader().end(),
326 [context, header](const std::pair<std::string, std::string> &p) {
327 if (!p.first.empty() && !p.second.empty()) {
328 NapiUtils::SetStringPropertyUtf8(context->GetEnv(), header, p.first, p.second);
329 }
330 });
331 }
332 return header;
333 }
334
IsUnReserved(unsigned char in)335 bool FetchExec::IsUnReserved(unsigned char in)
336 {
337 if ((in >= '0' && in <= '9') || (in >= 'a' && in <= 'z') || (in >= 'A' && in <= 'Z')) {
338 return true;
339 }
340 switch (in) {
341 case '-':
342 case '.':
343 case '_':
344 case '~':
345 return true;
346 default:
347 break;
348 }
349 return false;
350 }
351 } // namespace OHOS::NetStack::Fetch
352