* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <array>
#include <iostream>
#include "webview_controller.h"
#include "proxy_config.h"
#include "proxy_rule.h"
#include "ani_business_error.h"
#include "ani_parse_utils.h"
#include "nweb_log.h"
#include "web_errors.h"
namespace OHOS {
namespace NWeb {
using namespace NWebError;
using NWebError::NO_ERROR;
namespace {
const char* WEB_PROXY_CONFIG_CLASS_NAME = "@ohos.web.webview.webview.ProxyConfig";
const char* WEB_PROXY_RULE_CLASS_NAME = "@ohos.web.webview.webview.ProxyRule";
const char* WEB_PROXY_SCHEME_FILTER_ENUM_NAME = "@ohos.web.webview.webview.ProxySchemeFilter";
}
static void JsInsertProxyRule(ani_env *env, ani_object object, ani_string url, ani_enum_item schemeFilter)
{
WVLOG_D("[PROXYCONTROLLER] JsInsertProxyRule.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return;
}
ani_boolean isUndefined = ANI_TRUE;
env->Reference_IsUndefined(url, &isUndefined);
if (isUndefined == ANI_TRUE) {
WVLOG_E("[PROXYCONTROLLER] url is undefined");
return;
}
std::string proxyUrl;
if (!AniParseUtils::ParseString(env, url, proxyUrl)) {
WVLOG_E("[PROXYCONTROLLER] Parse url failed.");
AniBusinessError::ThrowError(env, NWebError::PARAM_CHECK_ERROR,
NWebError::FormatString(ParamCheckErrorMsgTemplate::TYPE_ERROR, "url", "string"));
return;
}
auto* proxyConfig = reinterpret_cast<ProxyConfig *>(AniParseUtils::Unwrap(env, object));
if (!proxyConfig) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return;
}
int32_t proxySchemeFilter = static_cast<int32_t>(ProxySchemeFilter::MATCH_ALL_SCHEMES);
isUndefined = ANI_TRUE;
env->Reference_IsUndefined(schemeFilter, &isUndefined);
if (isUndefined != ANI_TRUE) {
ani_int enumValue;
if (env->EnumItem_GetValue_Int(schemeFilter, &enumValue) != ANI_OK) {
AniBusinessError::ThrowError(env, NWebError::PARAM_CHECK_ERROR,
NWebError::FormatString(ParamCheckErrorMsgTemplate::TYPE_ERROR, "schemeFilter", "ProxySchemeFilter"));
return;
}
proxySchemeFilter = static_cast<int32_t>(enumValue);
}
WVLOG_D("[PROXYCONTROLLER] insert proxy rule %{public}s : %{public}d.", proxyUrl.c_str(), proxySchemeFilter);
proxyConfig->InsertProxyRule(proxyUrl, proxySchemeFilter);
}
static ani_ref GetProxyRulesInternal(ani_env *env, ProxyConfig* proxyConfig,
ani_class arrayCls, ani_class itemCls, ani_enum enumType)
{
ani_status status;
std::vector<ProxyRule> proxyRules = proxyConfig->GetProxyRules();
size_t rulesSize = proxyRules.size();
ani_method arrayCtor;
if (env->Class_FindMethod(arrayCls, "<ctor>", "i:", &arrayCtor) != ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] FindClass Lstd/core/Array; Failed.");
return nullptr;
}
ani_object arrayObj;
if (env->Object_New(arrayCls, arrayCtor, &arrayObj, rulesSize) != ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] Object_New Array Faild.");
return arrayObj;
}
for (size_t i = 0; i < rulesSize; i++) {
ani_method ctor;
if ((status = env->Class_FindMethod(itemCls, "<ctor>",
"C{std.core.String}C{@ohos.web.webview.webview.ProxySchemeFilter}:", &ctor)) != ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] Class_FindMethod status: %{public}d", status);
return arrayObj;
}
ani_string itemString{};
if ((status = env->String_NewUTF8(proxyRules[i].GetUrl().c_str(),
proxyRules[i].GetUrl().size(), &itemString)) != ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] String_NewUTF8 status: %{public}d", status);
return arrayObj;
}
ani_enum_item itemEnum;
if ((status = env->Enum_GetEnumItemByIndex(enumType, ani_size(proxyRules[i].GetSchemeFilter()), &itemEnum))
!= ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] Enum_GetEnumItemByIndex status: %{public}d", status);
return arrayObj;
}
ani_object inputObject = nullptr;
if ((status = env->Object_New(itemCls, ctor, &inputObject, itemString, itemEnum)) != ANI_OK ||
inputObject == nullptr) {
WVLOG_E("[PROXYCONTROLLER] Object_New status: %{public}d", status);
return arrayObj;
}
if (env->Object_CallMethodByName_Void(arrayObj, "$_set", "iY:", i, inputObject) != ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] Object_New status: %{public}d", status);
return arrayObj;
}
}
return arrayObj;
}
static ani_ref JsGetProxyRule(ani_env *env, ani_object object)
{
WVLOG_D("[PROXYCONTROLLER] JsGetProxyRule.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return nullptr;
}
auto* proxyConfig = reinterpret_cast<ProxyConfig *>(AniParseUtils::Unwrap(env, object));
if (!proxyConfig) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return nullptr;
}
ani_class itemCls;
ani_status status;
if ((status = env->FindClass(WEB_PROXY_RULE_CLASS_NAME, &itemCls)) != ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] FindClass status: %{public}d", status);
return nullptr;
}
ani_enum enumType;
if ((status = env->FindEnum(WEB_PROXY_SCHEME_FILTER_ENUM_NAME, &enumType)) != ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] FindEnum status: %{public}d", status);
return nullptr;
}
ani_class arrayCls = nullptr;
if (env->FindClass("std.core.Array", &arrayCls) != ANI_OK) {
WVLOG_E("[PROXYCONTROLLER] FindClass Lstd/core/Array; Failed.");
return nullptr;
}
return GetProxyRulesInternal(env, proxyConfig, arrayCls, itemCls, enumType);
}
static void JsInsertBypassRule(ani_env* env, ani_object object, ani_string bypassRule)
{
WVLOG_D("[PROXYCONTROLLER] JsInsertBypassRule.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return;
}
ProxyConfig* proxyConfig = reinterpret_cast<ProxyConfig*>(AniParseUtils::Unwrap(env, object));
if (proxyConfig == nullptr) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return;
}
std::string bypass;
if (!AniParseUtils::ParseString(env, bypassRule, bypass)) {
AniBusinessError::ThrowError(env, PARAM_CHECK_ERROR,
NWebError::FormatString(ParamCheckErrorMsgTemplate::TYPE_ERROR, "bypassRule", "string"));
return;
}
WVLOG_D("[PROXYCONTROLLER] insert bypass rule %{public}s.", bypass.c_str());
proxyConfig->InsertBypassRule(bypass);
return;
}
static void JsInsertDirectRule(ani_env* env, ani_object object, ani_enum_item schemeFilter)
{
WVLOG_D("[PROXYCONTROLLER] JsInsertDirectRule.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return;
}
ProxyConfig* proxyConfig = reinterpret_cast<ProxyConfig*>(AniParseUtils::Unwrap(env, object));
if (proxyConfig == nullptr) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return;
}
ani_boolean isUndefined = ANI_TRUE;
int32_t proxySchemeFilter = static_cast<int32_t>(ProxySchemeFilter::MATCH_ALL_SCHEMES);
env->Reference_IsUndefined(schemeFilter, &isUndefined);
if (isUndefined != ANI_TRUE) {
ani_int enumValue;
if (env->EnumItem_GetValue_Int(schemeFilter, &enumValue) != ANI_OK) {
AniBusinessError::ThrowError(env, NWebError::PARAM_CHECK_ERROR,
NWebError::FormatString(ParamCheckErrorMsgTemplate::TYPE_ERROR, "schemeFilter", "ProxySchemeFilter"));
return;
}
proxySchemeFilter = static_cast<int32_t>(enumValue);
}
WVLOG_D("[PROXYCONTROLLER] insert direct rule %{public}d.", proxySchemeFilter);
proxyConfig->InsertDirectRule(proxySchemeFilter);
return;
}
static void JsBypassHostnamesWithoutPeriod(ani_env* env, ani_object object)
{
WVLOG_D("[PROXYCONTROLLER] JsBypassHostnamesWithoutPeriod.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return;
}
ProxyConfig* proxyConfig = reinterpret_cast<ProxyConfig*>(AniParseUtils::Unwrap(env, object));
if (proxyConfig == nullptr) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return;
}
WVLOG_D("[PROXYCONTROLLER] bypass host names without period.");
proxyConfig->BypassHostnamesWithoutPeriod();
return;
}
static void JsClearImplicitRules(ani_env* env, ani_object object)
{
WVLOG_D("[PROXYCONTROLLER] JsClearImplicitRules.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return;
}
ProxyConfig* proxyConfig = reinterpret_cast<ProxyConfig*>(AniParseUtils::Unwrap(env, object));
if (proxyConfig == nullptr) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return;
}
WVLOG_D("[PROXYCONTROLLER] clear implicit rules.");
proxyConfig->ClearImplicitRules();
return;
}
static void JsEnableReverseBypass(ani_env* env, ani_object object, ani_boolean reverse)
{
WVLOG_D("[PROXYCONTROLLER] JsEnableReverseBypass.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return;
}
ProxyConfig* proxyConfig = reinterpret_cast<ProxyConfig*>(AniParseUtils::Unwrap(env, object));
if (proxyConfig == nullptr) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return;
}
bool enableReverseBypass = static_cast<bool>(reverse);
WVLOG_D("[PROXYCONTROLLER] enable reverse bypass %{public}d.", enableReverseBypass);
proxyConfig->EnableReverseBypass(enableReverseBypass);
return;
}
static ani_object CreateAniStringArray(ani_env* env, const std::vector<std::string>& arr)
{
WVLOG_D("[PROXYCONTROLLER] CreateAniStringArray.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return nullptr;
}
ani_ref undefinedRef = nullptr;
if (ANI_OK != env->GetUndefined(&undefinedRef)) {
WVLOG_E("GetUndefined Failed.");
return nullptr;
}
ani_array array;
if (ANI_OK != env->Array_New(arr.size(), undefinedRef, &array)) {
WVLOG_E("new array ref error.");
return array;
}
for (size_t i = 0; i < arr.size(); ++i) {
ani_string result {};
if (ANI_OK != env->String_NewUTF8(arr[i].c_str(), arr[i].size(), &result)) {
continue;
}
if (ANI_OK != env->Array_Set(array, i, result)) {
return array;
}
}
WVLOG_D("[PROXYCONTROLLER] CreateAniStringArray Exit");
return array;
}
static ani_object JsGetBypassRules(ani_env* env, ani_object object)
{
WVLOG_D("[PROXYCONTROLLER] JsGetBypassRules.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return nullptr;
}
ProxyConfig* proxyConfig = reinterpret_cast<ProxyConfig*>(AniParseUtils::Unwrap(env, object));
if (proxyConfig == nullptr) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return nullptr;
}
std::vector<std::string> bypassRules = proxyConfig->GetBypassRules();
return CreateAniStringArray(env, bypassRules);
}
static ani_boolean JsIsReverseBypassEnabled(ani_env* env, ani_object object)
{
WVLOG_D("[PROXYCONTROLLER] JsIsReverseBypassEnabled.");
if (env == nullptr) {
WVLOG_E("[PROXYCONTROLLER] env is nullptr");
return ANI_FALSE;
}
ProxyConfig* proxyConfig = reinterpret_cast<ProxyConfig*>(AniParseUtils::Unwrap(env, object));
if (proxyConfig == nullptr) {
WVLOG_E("[PROXYCONTROLLER] proxyConfig is null");
return ANI_FALSE;
}
ani_boolean result = ANI_FALSE;
bool enabled = proxyConfig->IsReverseBypassEnabled();
WVLOG_D("[PROXYCONTROLLER] is reverse bypass enabled %{public}d", enabled);
result = static_cast<ani_boolean>(enabled);
return result;
}
static void Constructor(ani_env *env, ani_object object)
{
if (env == nullptr) {
WVLOG_E("env is nullptr");
return;
}
ProxyConfig* proxyConfig = new (std::nothrow) ProxyConfig();
if (proxyConfig == nullptr) {
WVLOG_E("new ProxyConfig failed");
return;
}
if (!AniParseUtils::Wrap(env, object, WEB_PROXY_CONFIG_CLASS_NAME,
reinterpret_cast<ani_long>(proxyConfig))) {
WVLOG_E("ProxyConfig wrap failed");
delete proxyConfig;
proxyConfig = nullptr;
}
}
ani_status StsWebProxyConfigInit(ani_env *env)
{
if (env == nullptr) {
WVLOG_E("env is nullptr");
return ANI_ERROR;
}
ani_class proxyConfigCls = nullptr;
ani_status status = env->FindClass(WEB_PROXY_CONFIG_CLASS_NAME, &proxyConfigCls);
if (status != ANI_OK || !proxyConfigCls) {
WVLOG_E("find %{public}s class failed, status: %{public}d", WEB_PROXY_CONFIG_CLASS_NAME, status);
return ANI_ERROR;
}
std::array allMethods = {
ani_native_function { "<ctor>", nullptr, reinterpret_cast<void*>(Constructor) },
ani_native_function { "insertProxyRule", nullptr, reinterpret_cast<void*>(JsInsertProxyRule) },
ani_native_function { "getProxyRules", nullptr, reinterpret_cast<void*>(JsGetProxyRule) },
ani_native_function { "insertBypassRule", nullptr, reinterpret_cast<void*>(JsInsertBypassRule) },
ani_native_function { "insertDirectRule", nullptr, reinterpret_cast<void*>(JsInsertDirectRule) },
ani_native_function {
"bypassHostnamesWithoutPeriod", nullptr, reinterpret_cast<void*>(JsBypassHostnamesWithoutPeriod) },
ani_native_function { "clearImplicitRules", nullptr, reinterpret_cast<void*>(JsClearImplicitRules) },
ani_native_function { "enableReverseBypass", nullptr, reinterpret_cast<void*>(JsEnableReverseBypass) },
ani_native_function { "getBypassRules", nullptr, reinterpret_cast<void*>(JsGetBypassRules) },
ani_native_function { "isReverseBypassEnabled", nullptr, reinterpret_cast<void*>(JsIsReverseBypassEnabled) },
};
status = env->Class_BindNativeMethods(proxyConfigCls, allMethods.data(), allMethods.size());
if (status != ANI_OK) {
WVLOG_E("Class_BindNativeMethods failed status: %{public}d", status);
}
return ANI_OK;
}
}
}