diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d2ff20141ceed86d87c0ea5d99481973005bab2b..0000000000000000000000000000000000000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -/node_modules -/oh_modules -/local.properties -/.idea -**/build -/.hvigor -.cxx -/.clangd -/.clang-format -/.clang-tidy -**/.test -/.appanalyzer \ No newline at end of file diff --git a/LocalServer/headerServer.js b/LocalServer/headerServer.js index e30128b6a1561741e0f06a8f4b998eca3470e041..02c5deb79d361b15862e71756708b6d1894d4fc0 100644 --- a/LocalServer/headerServer.js +++ b/LocalServer/headerServer.js @@ -147,12 +147,13 @@ function getLocalNetworkIP() { const interfaces = os.networkInterfaces(); for (const name of Object.keys(interfaces)) { const iface = interfaces[name]; - if (iface) { - for (const addr of iface) { - // Skip internal (loopback) and non-IPv4 addresses - if (addr.family === 'IPv4' && !addr.internal) { - return addr.address; - } + if (!iface) { + continue; + } + for (const addr of iface) { + // Skip internal (loopback) and non-IPv4 addresses + if (addr.family === 'IPv4' && !addr.internal) { + return addr.address; } } } diff --git a/README.md b/README.md index a88afa0a876711dc9f231073519c85b217ad6799..d02fd41ed51db7f852f61113866811f44818d33e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ 3. 运行服务端代码:在根目录下打开DevEco Studio的Terminal,执行`hvigorw startHeaderServer`命令,启动本地服务器。 ![image](screenshots/readme/start_server.png) -4. 连接服务器地址:将Terminal中Server URL后面的URL复制到src/main/ets/common/CommonConstants.ets文件下的COMMON_HEADER_REQUEST_URL中,保存好后即可运行本工程进行测试。 +4. 连接服务器地址:将Terminal中Server URL后面的URL复制到[src/main/ets/common/CommonConstants.ets](./entry/src/main/ets/common/CommonConstants.ets)文件下的COMMON_HEADER_REQUEST_URL中,保存好后即可运行本工程进行测试。 ![image](screenshots/readme/server_url.png) 注:也可以直接运行本工程,在配置公共请求头页面的输入框中,手动输入运行服务端代码电脑的IP地址后进行访问请求。可以在Terminal中Local Network IP后面读取IP地址,或者在命令行工具中通过`ipconfig`命令查看IP地址。 diff --git a/entry/src/main/ets/Interceptors/CustomLoadingStrategyInterceptor/model/CustomLoadingStrategyModel.ets b/entry/src/main/ets/Interceptors/CustomLoadingStrategyInterceptor/model/CustomLoadingStrategyModel.ets index cdef8863c40e312a7d6167c4afaa560404faf6cf..ca6e9c9d460bdd825386e3b04aef89a6112b7ff0 100644 --- a/entry/src/main/ets/Interceptors/CustomLoadingStrategyInterceptor/model/CustomLoadingStrategyModel.ets +++ b/entry/src/main/ets/Interceptors/CustomLoadingStrategyInterceptor/model/CustomLoadingStrategyModel.ets @@ -69,8 +69,8 @@ export class CustomLoadingStrategyModel { createPlaceholderResponse(): WebResourceResponse { const response = new WebResourceResponse(); response.setResponseHeader([{ - headerKey: "Connection", - headerValue: "keep-alive" + headerKey: 'Connection', + headerValue: 'keep-alive' }]); response.setResponseData($rawfile(CommonConstants.IMAGE_NO_WLAN)); response.setResponseEncoding('utf-8'); diff --git a/entry/src/main/ets/Interceptors/LocalResourceInterceptor/model/LocalResourceModel.ets b/entry/src/main/ets/Interceptors/LocalResourceInterceptor/model/LocalResourceModel.ets index 6e0a62238b7e247b7f05c6b811a36be249b3cb05..38297f8a52e0706e1e6b352c62519ddc7e8154cf 100644 --- a/entry/src/main/ets/Interceptors/LocalResourceInterceptor/model/LocalResourceModel.ets +++ b/entry/src/main/ets/Interceptors/LocalResourceInterceptor/model/LocalResourceModel.ets @@ -58,8 +58,8 @@ export class LocalResourceModel { createLocalResourceResponse(rawfileName: string, mimeType: string): WebResourceResponse { const response = new WebResourceResponse(); response.setResponseHeader([{ - headerKey: "Connection", - headerValue: "keep-alive" + headerKey: 'Connection', + headerValue: 'keep-alive' }]); response.setResponseData($rawfile(rawfileName)); response.setResponseEncoding('utf-8'); diff --git a/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/model/PageWhitelistModel.ets b/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/model/PageWhitelistModel.ets index 11bbc2eb70a07d0486092dae1d07b6753c3f5199..06aef42a71731e9e9bb14ce25cad6b20a87a3115 100644 --- a/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/model/PageWhitelistModel.ets +++ b/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/model/PageWhitelistModel.ets @@ -19,9 +19,10 @@ import { WhitelistDialogHandler, WhitelistDialogConfig } from '../../../componen * Model for PageWhitelist scene */ export class PageWhitelistModel { - private whitelistUrls: string[] = []; + private whitelistDomains: string[] = []; private dialogHandler: WhitelistDialogHandler; private closeWebpageCallback?: () => void; + private allowAllForCurrentLoad: boolean = false; constructor(dialogHandler: WhitelistDialogHandler, closeWebpageCallback?: () => void) { this.dialogHandler = dialogHandler; @@ -32,14 +33,39 @@ export class PageWhitelistModel { * Updates the whitelist URLs */ setWhitelistUrls(urls: string[]): void { - this.whitelistUrls = urls; + this.whitelistDomains = urls + .map(url => this.extractDomain(url)) + .filter(domain => domain.length > 0); } + /** + * Reset session-level allow flags when starting a new load + */ + resetSession(): void { + this.allowAllForCurrentLoad = false; + } + + /** * Checks if a URL is in the whitelist */ isUrlInWhitelist(requestUrl: string): boolean { - return this.whitelistUrls.some(valid => requestUrl.startsWith(valid)); + const requestDomain = this.extractDomain(requestUrl); + if (!requestDomain) { + return false; + } + return this.whitelistDomains.includes(requestDomain); + } + + private extractDomain(url: string): string { + let normalized = url.trim().toLowerCase(); + if (!normalized) { + return ''; + } + normalized = normalized + .replace(/^(?:[a-z0-9+.-]+:)?\/\//, '') // strip protocol-like prefixes + .split(/[/?#]/)[0]; // drop everything after domain + return normalized.replace(/:+$/, '').replace(/\/+$/, ''); } /** @@ -78,9 +104,14 @@ export class PageWhitelistModel { processLoadIntercept(event: OnLoadInterceptEvent): boolean { const requestUrl = event.data.getRequestUrl(); + if (this.allowAllForCurrentLoad) { + return false; + } + // Check if URL is in whitelist if (this.isUrlInWhitelist(requestUrl)) { - return false; // Allow loading + this.allowAllForCurrentLoad = true; + return false; // Allow loading and subsequent requests } // URL not in whitelist, show dialog diff --git a/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/view/PageWhitelistView.ets b/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/view/PageWhitelistView.ets index e8f277997f2ba5d90bee32d530e3435b43fe8647..c9b56e66547c5f7e4e40ce664bfe06d2782bf9f6 100644 --- a/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/view/PageWhitelistView.ets +++ b/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/view/PageWhitelistView.ets @@ -112,6 +112,7 @@ struct PageWhitelist { } // Parse URLs in the whitelist. this.whitelistUrlArr = this.whitelistUrl.toString().split(','); + this.viewModel?.resetSession(); this.isLoading = true; }) } diff --git a/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/viewmodel/PageWhitelistViewModel.ets b/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/viewmodel/PageWhitelistViewModel.ets index 1d60e30ecb83b2301efc4484bf555204133c2705..e1e0486f72fb3a1cb60097f502b1a9aa7b041d6a 100644 --- a/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/viewmodel/PageWhitelistViewModel.ets +++ b/entry/src/main/ets/Interceptors/PageWhitelistInterceptor/viewmodel/PageWhitelistViewModel.ets @@ -33,6 +33,13 @@ export class PageWhitelistViewModel { this.model.setWhitelistUrls(urls); } + /** + * Reset whitelist session state for a fresh load + */ + resetSession(): void { + this.model.resetSession(); + } + /** * Handles load intercept logic */ diff --git a/entry/src/main/ets/Interceptors/RedirectRequestInterceptor/model/RedirectRequestModel.ets b/entry/src/main/ets/Interceptors/RedirectRequestInterceptor/model/RedirectRequestModel.ets index 4afdcaa2868fbcf453227af9d4630cad8c319cce..7ccaf2004f7c5de10a9aaa7c305f81ef18941e43 100644 --- a/entry/src/main/ets/Interceptors/RedirectRequestInterceptor/model/RedirectRequestModel.ets +++ b/entry/src/main/ets/Interceptors/RedirectRequestInterceptor/model/RedirectRequestModel.ets @@ -37,11 +37,27 @@ export class RedirectRequestModel { this.redirectUrl = redirectUrl; } + /** + * Normalizes the URL + */ + private normalizeUrl(url: string): string { + return url + .replace(/^(?:[a-zA-Z]+:)?\/\//, '') + .replace(/\/+$/, '') + .trim(); + } + /** * Checks if the URL needs to be intercepted and redirected */ shouldInterceptUrl(requestUrl: string): boolean { - return !requestUrl.replace(/^(?:[a-zA-Z]+:)?\/\//, '').startsWith('www.example.com/'); + if (!this.redirectUrl) { + return false; + } + const normalizedRequest = this.normalizeUrl(requestUrl); + const normalizedRedirect = this.normalizeUrl(this.redirectUrl); + const isRedirectTarget = normalizedRequest === normalizedRedirect; + return !isRedirectTarget; } /** diff --git a/entry/src/main/ets/common/utils/Logger.ets b/entry/src/main/ets/common/utils/Logger.ets index cf240ea9fc467f810bf698c3cc8c882f2f55dd5e..9e06287ba1709d4c7263f98116c03b59056a1c45 100644 --- a/entry/src/main/ets/common/utils/Logger.ets +++ b/entry/src/main/ets/common/utils/Logger.ets @@ -49,4 +49,4 @@ class Logger { } } -export default new Logger('WebCrossDomain'); \ No newline at end of file +export default new Logger('WebInterceptor'); \ No newline at end of file diff --git a/entry/src/main/ets/common/utils/RcpRequestForwarder.ets b/entry/src/main/ets/common/utils/RcpRequestForwarder.ets index 3ce454fac3de9fc775f74fdc719fe99fe60d0c42..2221995e963a9a769da5a6f1c6c7638c866fa561 100644 --- a/entry/src/main/ets/common/utils/RcpRequestForwarder.ets +++ b/entry/src/main/ets/common/utils/RcpRequestForwarder.ets @@ -159,7 +159,7 @@ export class RcpRequestForwarder { }); break; case 'PATCH': - let request = new rcp.Request(targetUrl, "PATCH", headers); + let request = new rcp.Request(targetUrl, 'PATCH', headers); this.session?.fetch(request).then((response: rcp.Response) => { Logger.info(TAG, `${method} success: ${targetUrl}`); this.handleResponse(response, resourceHandler); diff --git a/screenshots/device/CommonHeaderResult.png b/screenshots/device/CommonHeaderResult.png index a013f9839eab0a160566f25ab9d0ab0e383e4452..6a531fa6b764e9872e47b073659fff0c77b83f2c 100644 Binary files a/screenshots/device/CommonHeaderResult.png and b/screenshots/device/CommonHeaderResult.png differ diff --git a/scripts/commandTask.ts b/scripts/commandTask.ts index 04b8cd00fa3f08a62de682178a41f981e6bd5aa0..9342fe025025e033775fb6e63e0f25e0da32a298 100644 --- a/scripts/commandTask.ts +++ b/scripts/commandTask.ts @@ -15,73 +15,81 @@ const { execSync } = require('child_process'); const os = require('os'); + +const IPV4_REGEX = /^\d+\.\d+\.\d+\.\d+$/; +const LOOPBACK_PREFIX = '127.'; let headerServerFlag: boolean = false; -// ==================== Header Server (Port 8081) ==================== +function pickValidIp(candidates: string[] = []): string | undefined { + return candidates + .map(ip => ip?.trim()) + .find(ip => ip && !ip.startsWith(LOOPBACK_PREFIX) && IPV4_REGEX.test(ip)); +} -/** - * Get local network IP address - * Returns the first non-internal IPv4 address found - */ -export function getLocalNetworkIP(): string { +function safeExec(command: string): string | undefined { + try { + return execSync(command, { encoding: 'utf8', shell: true }).trim(); + } catch { + return undefined; + } +} + +function getIpFromInterfaces(): string | undefined { try { const interfaces = os.networkInterfaces(); - for (const name of Object.keys(interfaces)) { - const iface = interfaces[name]; - if (iface) { - for (const addr of iface) { - // Skip internal (loopback) and non-IPv4 addresses - if (addr.family === 'IPv4' && !addr.internal) { - return addr.address; - } - } + for (const iface of Object.values(interfaces)) { + if (!iface) { + continue; + } + const hit = iface.find(info => info.family === 'IPv4' && !info.internal); + if (hit) { + return hit.address; } } } catch (err) { - console.error('Failed to get network IP:', err); + console.error('Failed to get network IP from interfaces:', err); + } + return undefined; +} + +function getIpFromWindowsCommand(): string | undefined { + const output = safeExec('for /f "tokens=2 delims=:" %a in (\'ipconfig ^| findstr /i "IPv4"\') do @echo %a'); + return output ? pickValidIp(output.split('\n')) : undefined; +} + +function getIpFromUnixCommands(): string | undefined { + const hostnameResult = safeExec('hostname -I'); + const hostnameIp = hostnameResult ? pickValidIp(hostnameResult.split(/\s+/)) : undefined; + if (hostnameIp) { + return hostnameIp; } - - // Fallback: try to get IP via system command + + const ipCommandResult = safeExec("ip -4 addr show | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}'"); + return ipCommandResult ? pickValidIp(ipCommandResult.split(/\s+/)) : undefined; +} + +function getIpFromSystemCommands(): string | undefined { try { - const isWindows = os.platform() === 'win32'; - let command: string; - if (isWindows) { - // Windows: get IP from ipconfig - command = 'for /f "tokens=2 delims=:" %a in (\'ipconfig ^| findstr /i "IPv4"\') do @echo %a'; - const result = execSync(command, { encoding: 'utf8', shell: true }); - const lines = result.trim().split('\n'); - for (const line of lines) { - const ip = line.trim(); - // Check if it's a valid IP and not loopback - if (ip && !ip.startsWith('127.') && ip.match(/^\d+\.\d+\.\d+\.\d+$/)) { - return ip; - } - } - } else { - // Linux/Mac: get IP from hostname -I or ip command - try { - const result = execSync('hostname -I', { encoding: 'utf8' }); - const ips = result.trim().split(/\s+/); - for (const ip of ips) { - if (ip && !ip.startsWith('127.') && ip.match(/^\d+\.\d+\.\d+\.\d+$/)) { - return ip; - } - } - } catch { - // Fallback to ip command - const result = execSync("ip -4 addr show | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}'", { encoding: 'utf8' }, { shell: true }); - const ips = result.trim().split('\n'); - for (const ip of ips) { - if (ip && !ip.startsWith('127.')) { - return ip; - } - } - } - } + return os.platform() === 'win32' ? getIpFromWindowsCommand() : getIpFromUnixCommands(); } catch (err) { console.error('Failed to get IP via system command:', err); + return undefined; } - +} + +/** + * Get local network IP address + * Returns the first non-internal IPv4 address found + */ +export function getLocalNetworkIP(): string { + const sources = [getIpFromInterfaces, getIpFromSystemCommands]; + for (const source of sources) { + const ip = source(); + if (ip) { + return ip; + } + } + // Ultimate fallback return '127.0.0.1'; }