Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions ios/README_iOS_CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ OpenList需要访问本地HTTP服务(如 `http://127.0.0.1`),这在iOS中
```xml
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
Expand All @@ -103,13 +101,6 @@ OpenList需要访问本地HTTP服务(如 `http://127.0.0.1`),这在iOS中
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
<key>0.0.0.0</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
</dict>
</dict>
```
Expand All @@ -118,15 +109,14 @@ OpenList需要访问本地HTTP服务(如 `http://127.0.0.1`),这在iOS中
1. **iOS App Transport Security (ATS)** 默认阻止所有HTTP连接
2. **本地服务访问** - OpenList需要连接到 `http://127.0.0.1` 的本地服务
3. **开发和调试** - 允许连接到本地开发服务器
4. **安全性平衡** - 只允许特定的本地地址,而不是完全禁用ATS
4. **安全性平衡** - 仅为本地环回地址提供例外,保持 ATS 默认保护

### 支持的本地地址:
- `localhost` - 标准本地主机名
- `127.0.0.1` - IPv4回环地址
- `0.0.0.0` - 所有接口绑定地址

### 生产环境建议:
如果生产版本不需要访问本地HTTP服务,可以考虑移除 `NSAllowsArbitraryLoads` 配置,只保留特定域名的例外
如果生产版本不需要访问本地HTTP服务,可以进一步移除 `NSExceptionDomains` 的本地例外配置

## 8. 后台模式配置(如需要)
在Info.plist中添加后台模式:
Expand Down
9 changes: 0 additions & 9 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
Expand All @@ -85,13 +83,6 @@
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
<key>0.0.0.0</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
</dict>
</dict>
<key>UIBackgroundModes</key>
Expand Down
94 changes: 65 additions & 29 deletions lib/pages/web/web.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:developer';
import 'dart:io' show Platform;

import 'package:openlist_mobile/contant/native_bridge.dart';
import 'package:openlist_mobile/generated_api.dart';
Expand Down Expand Up @@ -34,7 +35,7 @@ class WebScreenState extends State<WebScreen> with WidgetsBindingObserver {
// iOS specific: Enable page caching and state preservation
cacheEnabled: true,
sharedCookiesEnabled: true,
limitsNavigationsToAppBoundDomains: false,
limitsNavigationsToAppBoundDomains: Platform.isIOS,
// Enable disk and memory cache for better state preservation
cacheMode: CacheMode.LOAD_DEFAULT,
// Prevent WebView from being suspended in background
Expand All @@ -48,6 +49,56 @@ class WebScreenState extends State<WebScreen> with WidgetsBindingObserver {
bool _canGoBack = false;
bool _isLoading = false;

static const Set<String> _inAppSafeSchemes = {
"about",
"data",
"file",
};

bool _isLoopbackHost(String host) {
final normalized = host.toLowerCase();
return normalized == "localhost" ||
normalized == "127.0.0.1" ||
normalized == "::1";
}

bool _isAllowedInAppNavigation(Uri uri) {
final scheme = uri.scheme.toLowerCase();

if (_inAppSafeSchemes.contains(scheme)) {
return true;
}

if (scheme == "http" || scheme == "https") {
if (!Platform.isIOS) {
return true;
}
return _isLoopbackHost(uri.host);
}

return false;
}

Future<void> _openExternalUri(String uriString) async {
final silentMode = await NativeBridge.appConfig.isSilentJumpAppEnabled();
if (silentMode) {
NativeCommon().startActivityFromUri(uriString);
return;
}

if (!mounted) return;

Get.showSnackbar(GetSnackBar(
message: S.current.jumpToOtherApp,
duration: const Duration(seconds: 5),
mainButton: TextButton(
onPressed: () {
NativeCommon().startActivityFromUri(uriString);
},
child: Text(S.current.goTo),
)));
}

onClickNavigationBar() {
log("onClickNavigationBar");
_webViewController?.reload();
Expand Down Expand Up @@ -156,38 +207,23 @@ class WebScreenState extends State<WebScreen> with WidgetsBindingObserver {
shouldOverrideUrlLoading: (controller, navigationAction) async {
log("shouldOverrideUrlLoading ${navigationAction.request.url}");

var uri = navigationAction.request.url!;
if (![
"http",
"https",
"file",
"chrome",
"data",
"javascript",
"about"
].contains(uri.scheme)) {
log("shouldOverrideUrlLoading ${uri.toString()}");
final silentMode =
await NativeBridge.appConfig.isSilentJumpAppEnabled();
if (silentMode) {
NativeCommon().startActivityFromUri(uri.toString());
} else {
Get.showSnackbar(GetSnackBar(
message: S.current.jumpToOtherApp,
duration: const Duration(seconds: 5),
mainButton: TextButton(
onPressed: () {
NativeCommon()
.startActivityFromUri(uri.toString());
},
child: Text(S.current.goTo),
)));
}
final uri = navigationAction.request.url;
if (uri == null) {
return NavigationActionPolicy.CANCEL;
}

if (_isAllowedInAppNavigation(uri)) {
return NavigationActionPolicy.ALLOW;
}

final scheme = uri.scheme.toLowerCase();
if (Platform.isIOS && scheme == "javascript") {
log("Blocked javascript navigation on iOS: ${uri.toString()}");
return NavigationActionPolicy.CANCEL;
}

return NavigationActionPolicy.ALLOW;
await _openExternalUri(uri.toString());
return NavigationActionPolicy.CANCEL;
},
onReceivedError: (controller, request, error) async {
log("WebView error: ${error.description}");
Expand Down
Loading