use crate::webview::{WebTag, find_webview};
use crate::{WebResourceBody, WebResourceResponse};
use napi_ohos::Result as NapiResult;
use ohos_web_sys::*;
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io::Read;
use std::os::raw::{c_char, c_int};
use std::ptr;
pub unsafe extern "C" fn on_lx_request_start(
scheme_handler: *const ArkWeb_SchemeHandler,
resource_request: *mut ArkWeb_ResourceRequest,
resource_handler: *const ArkWeb_ResourceHandler,
intercept: *mut bool,
) {
if scheme_handler.is_null() || resource_request.is_null() || resource_handler.is_null() {
log::error!("Invalid parameters in scheme handler callback");
return;
}
let user_data = unsafe { OH_ArkWebSchemeHandler_GetUserData(scheme_handler) };
if user_data.is_null() {
log::error!("No user data found in scheme handler");
return;
}
let webtag_cstr = unsafe { CStr::from_ptr(user_data as *const c_char) };
let webtag_str = match webtag_cstr.to_str() {
Ok(s) => s,
Err(_) => {
log::error!("Invalid webtag in user data");
return;
}
};
let webtag = WebTag::from(webtag_str);
let mut url_ptr: *mut c_char = ptr::null_mut();
unsafe { OH_ArkWebResourceRequest_GetUrl(resource_request, &mut url_ptr) };
if url_ptr.is_null() {
log::error!("Failed to get request URL");
return;
}
let url = match unsafe { CStr::from_ptr(url_ptr).to_str() } {
Ok(s) => s.to_string(),
Err(_) => {
log::error!("Invalid UTF-8 in request URL");
unsafe { OH_ArkWeb_ReleaseString(url_ptr) };
return;
}
};
unsafe { OH_ArkWeb_ReleaseString(url_ptr) };
let mut method_ptr: *mut c_char = ptr::null_mut();
unsafe { OH_ArkWebResourceRequest_GetMethod(resource_request, &mut method_ptr) };
let method = if method_ptr.is_null() {
"GET".to_string()
} else {
let method_str = unsafe {
CStr::from_ptr(method_ptr)
.to_str()
.unwrap_or("GET")
.to_string()
};
unsafe { OH_ArkWeb_ReleaseString(method_ptr) };
method_str
};
log::info!(
"Processing request: {} {} for webtag: {}",
method,
url,
webtag.as_str()
);
let http_request = match http::Request::builder()
.method(method.as_str())
.uri(&url)
.body(Vec::new()) {
Ok(req) => req,
Err(e) => {
log::error!("Failed to build HTTP request: {}", e);
return; }
};
let scheme = url.split("://").next().unwrap_or("").to_string();
let http_response = if let Some(webview) = find_webview(&webtag) {
webview.handle_scheme_request(&scheme, http_request)
} else {
None
};
if let Some(http_response) = http_response {
unsafe {
*intercept = true;
send_response(resource_handler, http_response);
}
}
}
unsafe fn send_response(
resource_handler: *const ArkWeb_ResourceHandler,
http_response: WebResourceResponse,
) {
let (parts, body) = http_response.into_parts();
let mut headers_map = parts.headers.clone();
let mut response: *mut ArkWeb_Response = ptr::null_mut();
unsafe {
OH_ArkWeb_CreateResponse(&mut response);
}
unsafe {
OH_ArkWebResponse_SetStatus(response, parts.status.as_u16() as c_int);
}
if !headers_map.contains_key(http::header::CONTENT_LENGTH) {
let content_len = match &body {
WebResourceBody::Path(path) => std::fs::metadata(path).ok().map(|m| m.len()),
WebResourceBody::Bytes(data) => Some(data.len() as u64),
WebResourceBody::Pipe(_) => None,
};
if let Some(len) = content_len
&& let Ok(value) = http::HeaderValue::from_str(&len.to_string())
{
headers_map.insert(http::header::CONTENT_LENGTH, value);
}
}
for (key, value) in headers_map.iter() {
if let Ok(value_str) = value.to_str()
&& let (Ok(key_cstr), Ok(value_cstr)) =
(CString::new(key.as_str()), CString::new(value_str))
{
unsafe {
OH_ArkWebResponse_SetHeaderByName(
response,
key_cstr.as_ptr(),
value_cstr.as_ptr(),
true, );
}
}
}
unsafe {
OH_ArkWebResourceHandler_DidReceiveResponse(resource_handler, response);
}
match body {
WebResourceBody::Path(path) => {
let mut file = match File::open(&path) {
Ok(f) => f,
Err(e) => {
log::error!(
"Failed to open response file for Harmony webview: {} ({})",
path.display(),
e
);
unsafe {
OH_ArkWebResponse_SetStatus(response, 500);
}
let message = CString::new("Internal Server Error").unwrap();
unsafe {
OH_ArkWebResourceHandler_DidReceiveResponse(resource_handler, response);
OH_ArkWebResourceHandler_DidReceiveData(
resource_handler,
message.as_ptr(),
message.as_bytes().len() as i64,
);
OH_ArkWebResourceHandler_DidFinish(resource_handler);
OH_ArkWeb_DestroyResponse(response);
}
return;
}
};
let mut buffer = [0u8; 64 * 1024];
loop {
match file.read(&mut buffer) {
Ok(0) => break,
Ok(read_bytes) => unsafe {
OH_ArkWebResourceHandler_DidReceiveData(
resource_handler,
buffer.as_ptr(),
read_bytes as i64,
);
},
Err(e) => {
log::error!(
"Failed while streaming response data for Harmony webview: {}",
e
);
break;
}
}
}
}
WebResourceBody::Pipe(reader) => {
let mut file = reader.into_file();
let mut buffer = [0u8; 64 * 1024];
loop {
match file.read(&mut buffer) {
Ok(0) => break,
Ok(read_bytes) => unsafe {
OH_ArkWebResourceHandler_DidReceiveData(
resource_handler,
buffer.as_ptr(),
read_bytes as i64,
);
},
Err(e) => {
log::error!(
"Failed while streaming response data for Harmony webview: {}",
e
);
break;
}
}
}
}
WebResourceBody::Bytes(data) => {
if !data.is_empty() {
unsafe {
OH_ArkWebResourceHandler_DidReceiveData(
resource_handler,
data.as_ptr(),
data.len() as i64,
);
}
}
}
}
unsafe {
OH_ArkWebResourceHandler_DidFinish(resource_handler);
}
unsafe {
OH_ArkWeb_DestroyResponse(response);
}
}
pub unsafe extern "C" fn on_lx_request_stop(
_scheme_handler: *const ArkWeb_SchemeHandler,
resource_request: *const ArkWeb_ResourceRequest,
) {
log::debug!("Stopped request");
if !resource_request.is_null() {
unsafe {
OH_ArkWebResourceRequest_Destroy(resource_request);
}
}
}
pub unsafe fn cleanup_scheme_handler(scheme_handler: *mut ArkWeb_SchemeHandler) {
if scheme_handler.is_null() {
return;
}
unsafe {
let user_data = OH_ArkWebSchemeHandler_GetUserData(scheme_handler);
if !user_data.is_null() {
let _webtag_cstr = CString::from_raw(user_data as *mut c_char);
}
OH_ArkWeb_DestroySchemeHandler(scheme_handler);
}
}
pub fn register_custom_schemes() -> NapiResult<()> {
unsafe {
let lx_scheme_cstr = CString::new("lx").unwrap();
let options = 1 | 2 | 16 | 32 | 64;
OH_ArkWeb_RegisterCustomSchemes(lx_scheme_cstr.as_ptr(), options);
log::info!("Successfully registered custom scheme: lx with extended options");
Ok(())
}
}
pub fn set_webview_scheme_handler(webtag: &WebTag) -> NapiResult<()> {
let webview = find_webview(webtag).ok_or_else(|| {
napi_ohos::Error::new(
napi_ohos::Status::GenericFailure,
format!("WebView not found for tag: {}", webtag),
)
})?;
let schemes = &webview.effective_options().registered_schemes;
if schemes.is_empty() {
log::info!(
"No registered schemes for web_tag={}, skipping native handler setup",
webtag
);
return Ok(());
}
let webtag_cstr_for_set = CString::new(webtag.as_str()).unwrap();
let mut results: Vec<(String, bool)> = Vec::new();
for scheme_name in schemes {
unsafe {
let mut handler: *mut ArkWeb_SchemeHandler = std::ptr::null_mut();
OH_ArkWeb_CreateSchemeHandler(&mut handler);
let webtag_cstr = CString::new(webtag.as_str()).unwrap();
let webtag_ptr = webtag_cstr.into_raw();
OH_ArkWebSchemeHandler_SetUserData(handler, webtag_ptr as *mut std::ffi::c_void);
OH_ArkWebSchemeHandler_SetOnRequestStart(handler, Some(on_lx_request_start));
OH_ArkWebSchemeHandler_SetOnRequestStop(handler, Some(on_lx_request_stop));
let scheme_cstr = CString::new(scheme_name.as_str()).unwrap();
let success = OH_ArkWeb_SetSchemeHandler(
scheme_cstr.as_ptr(),
webtag_cstr_for_set.as_ptr(),
handler,
);
if success {
webview.inner.track_scheme_handler(handler);
} else {
log::error!(
"Failed to set {}:// scheme handler for web_tag: {}",
scheme_name,
webtag
);
cleanup_scheme_handler(handler);
if scheme_name != "https" {
return Err(napi_ohos::Error::new(
napi_ohos::Status::GenericFailure,
format!(
"Failed to set {}:// scheme handler for web_tag: {}",
scheme_name, webtag
),
));
}
}
results.push((scheme_name.clone(), success));
}
}
let summary: Vec<String> = results
.iter()
.map(|(s, ok)| format!("{}: {}", s, ok))
.collect();
log::info!(
"Scheme handlers for web_tag={}: [{}]",
webtag,
summary.join(", ")
);
Ok(())
}