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