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