1 /*
2 * Copyright (c) 2025 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 #ifdef NETMANAGER_ENABLE_PAC_PROXY
16 #include <cstring>
17 #include <string>
18 #include <curl/curl.h>
19 #include <jerryscript.h>
20
21 #include "net_pac_manager.h"
22 #include "pac_functions.h"
23 #include "netmanager_base_log.h"
24
25 namespace OHOS {
26 namespace NetManagerStandard {
27 namespace {
28 constexpr char NULL_CHAR = '\0';
29 constexpr const char FIND_FUNC[] = "FindProxyForURL";
30 constexpr const char USER_AGENT[] = "libcurl-agent/1.0";
31 const std::string TRUE = "true";
32 const std::string FALSE = "false";
33 const std::string EMPTY = "";
34 constexpr int32_t HTTP_CODE_200 = 200;
35 } // namespace
36
37 static std::string g_script;
38
NetPACManager()39 NetPACManager::NetPACManager() : pacScriptVal_(jerry_create_undefined()), status_(false), engineInitialized_(false)
40 {
41 }
42
~NetPACManager()43 NetPACManager::~NetPACManager()
44 {
45 std::lock_guard<std::mutex> guard{pacMutex_};
46 if (jerry_value_is_undefined(pacScriptVal_) == false) {
47 jerry_release_value(pacScriptVal_);
48 pacScriptVal_ = jerry_create_undefined();
49 }
50 if (engineInitialized_) {
51 jerry_cleanup();
52 engineInitialized_ = false;
53 }
54 }
55
InitPACScript(const std::string & script)56 bool NetPACManager::InitPACScript(const std::string &script)
57 {
58 std::lock_guard<std::mutex> guard{pacMutex_};
59 if (jerry_value_is_undefined(pacScriptVal_) == false) {
60 jerry_release_value(pacScriptVal_);
61 pacScriptVal_ = jerry_create_undefined();
62 }
63 if (engineInitialized_) {
64 jerry_cleanup();
65 engineInitialized_ = false;
66 }
67 const char *pac_script = script.c_str();
68 jerry_init(JERRY_INIT_EMPTY);
69 engineInitialized_ = true;
70 PacFunctions::RegisterPacFunctions();
71 pacScriptVal_ = jerry_parse(NULL, 0, (jerry_char_t *)pac_script, strlen(pac_script), JERRY_PARSE_NO_OPTS);
72 if (jerry_value_is_error(pacScriptVal_)) {
73 jerry_value_t error_value = jerry_get_value_from_error(pacScriptVal_, false);
74 jerry_release_value(pacScriptVal_);
75 jerry_release_value(error_value);
76 pacScriptVal_ = jerry_create_undefined();
77 status_ = false;
78 return false;
79 }
80 status_ = true;
81 return true;
82 }
83
InitPACScriptWithURL(const std::string & scriptUrl)84 bool NetPACManager::InitPACScriptWithURL(const std::string &scriptUrl)
85 {
86 scriptFileUrl_ = scriptUrl;
87 std::lock_guard<std::mutex> guard{pacMutex_};
88 if (jerry_value_is_undefined(pacScriptVal_) == false) {
89 jerry_release_value(pacScriptVal_);
90 pacScriptVal_ = jerry_create_undefined();
91 }
92 if (engineInitialized_) {
93 jerry_cleanup();
94 engineInitialized_ = false;
95 }
96 g_script.clear();
97 DownloadPACScript(scriptUrl);
98 if (g_script.empty()) {
99 status_ = false;
100 return false;
101 }
102 const char *pac_script = g_script.c_str();
103 jerry_init(JERRY_INIT_EMPTY);
104 engineInitialized_ = true;
105 PacFunctions::RegisterPacFunctions();
106 pacScriptVal_ = jerry_parse(NULL, 0, (jerry_char_t *)pac_script, strlen(pac_script), JERRY_PARSE_NO_OPTS);
107 if (jerry_value_is_error(pacScriptVal_)) {
108 jerry_value_t error_value = jerry_get_value_from_error(pacScriptVal_, false);
109 jerry_release_value(pacScriptVal_);
110 jerry_release_value(error_value);
111 pacScriptVal_ = jerry_create_undefined();
112 status_ = false;
113 return false;
114 }
115 status_ = true;
116 return true;
117 }
118
FindProxyForURL(const std::string & url,std::string & proxy)119 PAC_STATUS NetPACManager::FindProxyForURL(const std::string &url, std::string &proxy)
120 {
121 return FindProxyForURL(url, ParseHost(url), proxy);
122 }
123
ReleaseValues(const std::vector<jerry_value_t> & values)124 static void ReleaseValues(const std::vector<jerry_value_t> &values)
125 {
126 for (auto value : values) {
127 jerry_release_value(value);
128 }
129 }
130
FindProxyForURL(const std::string & url,const std::string & hostStr,std::string & proxy)131 PAC_STATUS NetPACManager::FindProxyForURL(const std::string &url, const std::string &hostStr, std::string &proxy)
132 {
133 if (!status_ && !InitPACScriptWithURL(scriptFileUrl_)) {
134 return PAC_SCRIPT_DOWNLOAD_ERROR;
135 }
136 std::lock_guard<std::mutex> guard{pacMutex_};
137 if (!engineInitialized_ || jerry_value_is_undefined(pacScriptVal_)) {
138 return PAC_SCRIPT_FUNCTION_ERROR;
139 }
140 std::string host = hostStr.empty() ? ParseHost(url) : hostStr;
141 jerry_value_t result = jerry_run(pacScriptVal_);
142 if (jerry_value_is_error(result)) {
143 jerry_value_t error_value = jerry_get_value_from_error(result, false);
144 jerry_release_value(result);
145 jerry_release_value(error_value);
146 return PAC_SCRIPT_RUN_ERROR;
147 }
148 jerry_value_t global_object = jerry_get_global_object();
149 jerry_value_t func_name = jerry_create_string(reinterpret_cast<const jerry_char_t *>(FIND_FUNC));
150 jerry_value_t func = jerry_get_property(global_object, func_name);
151 if (!jerry_value_is_function(func)) {
152 ReleaseValues({func, func_name, global_object, result});
153 return PAC_SCRIPT_FUNCTION_ERROR;
154 }
155 jerry_value_t args[2] = {jerry_create_string(reinterpret_cast<const jerry_char_t *>(url.c_str())),
156 jerry_create_string(reinterpret_cast<const jerry_char_t *>(host.c_str()))};
157 jerry_value_t call_result = jerry_call_function(func, global_object, args, 2);
158 PAC_STATUS status = PAC_OK;
159 if (!jerry_value_is_error(call_result)) {
160 if (jerry_value_is_string(call_result)) {
161 jerry_length_t strLength = jerry_get_string_length(call_result);
162 if (strLength > 0) {
163 char buffer[strLength + 1];
164 jerry_string_to_char_buffer(call_result, reinterpret_cast<jerry_char_t *>(buffer), strLength);
165 buffer[strLength] = NULL_CHAR;
166 proxy.append(buffer, strLength);
167 }
168 } else if (jerry_value_is_boolean(call_result)) {
169 proxy.append(jerry_get_boolean_value(call_result) ? TRUE : FALSE);
170 } else if (jerry_value_is_number(call_result)) {
171 proxy.append(std::to_string(static_cast<int>(jerry_get_number_value(call_result))));
172 }
173 } else {
174 jerry_value_t error_value = jerry_get_value_from_error(call_result, false);
175 jerry_release_value(error_value);
176 status = PAC_SCRIPT_CALL_ERROR;
177 }
178 ReleaseValues({call_result, args[0], args[1], func, func_name, global_object, result});
179 return status;
180 }
181
WriteToString(void * contents,size_t size,size_t nmemb,void * userp)182 static size_t WriteToString(void *contents, size_t size, size_t nmemb, void *userp)
183 {
184 size_t realsize = size * nmemb;
185 std::string *mem = static_cast<std::string *>(userp);
186 mem->append(static_cast<char *>(contents), realsize);
187 g_script.append(static_cast<char *>(contents), realsize);
188 return realsize;
189 }
190
DownloadPACScript(const std::string & url)191 void NetPACManager::DownloadPACScript(const std::string &url)
192 {
193 struct CurlGlobalGuard {
194 CurlGlobalGuard()
195 {
196 curl_global_init(CURL_GLOBAL_ALL);
197 }
198 ~CurlGlobalGuard()
199 {
200 curl_global_cleanup();
201 }
202 } curlGuard;
203 CURL *curl_handle = curl_easy_init();
204 if (!curl_handle) {
205 return;
206 }
207 struct CurlHandleDeleter {
208 void operator()(CURL *handle)
209 {
210 if (handle)
211 curl_easy_cleanup(handle);
212 }
213 };
214 std::unique_ptr<CURL, CurlHandleDeleter> handleGuard(curl_handle);
215 std::string data;
216 curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
217 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteToString);
218 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &data);
219 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, USER_AGENT);
220 curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 30L);
221 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
222 CURLcode res = curl_easy_perform(curl_handle);
223 if (res != CURLE_OK) {
224 NETMGR_LOG_E("CURL perform failed: %{private}s", curl_easy_strerror(res));
225 return;
226 }
227 int responseCode;
228 curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &responseCode);
229 if (responseCode != HTTP_CODE_200) {
230 NETMGR_LOG_E("HTTP request failed with status code: %ld", responseCode);
231 return;
232 }
233 if (data.empty()) {
234 NETMGR_LOG_E("Downloaded PAC script is empty");
235 return;
236 }
237 if (data.find("FindProxyForURL") == std::string::npos) {
238 NETMGR_LOG_W("Downloaded content may not be a valid PAC script (no FindProxyForURL function found)");
239 }
240 return;
241 }
242
243
SetFileUrl(const std::string & url)244 void NetPACManager::SetFileUrl(const std::string &url)
245 {
246 scriptFileUrl_ = url;
247 }
248
ParseHost(const std::string & url)249 std::string NetPACManager::ParseHost(const std::string &url)
250 {
251 CURLU *cu = curl_url();
252 if (!cu) {
253 return EMPTY;
254 }
255 struct CurlUrlDeleter {
256 void operator()(CURLU *handle)
257 {
258 if (handle)
259 curl_url_cleanup(handle);
260 }
261 };
262 std::unique_ptr<CURLU, CurlUrlDeleter> urlGuard(cu);
263 CURLUcode uc = curl_url_set(cu, CURLUPART_URL, url.c_str(), 0);
264 if (uc != CURLUE_OK) {
265 return EMPTY;
266 }
267 char *host = nullptr;
268 uc = curl_url_get(cu, CURLUPART_HOST, &host, 0);
269 std::string result;
270 if (uc == CURLUE_OK && host) {
271 result = host;
272 curl_free(host);
273 }
274 return result;
275 }
276 } // namespace NetManagerStandard
277 } // namespace OHOS
278 #endif