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