mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-02 11:05:38 +02:00
feat(network): add opt-in allow local/private network setting
Add a setting that relaxes the SSRF guard so extensions and built-in network code can reach private/local/loopback targets, for users routing traffic through a local proxy or custom DNS. Disabled by default. Wired end-to-end: Go backend (SetAllowPrivateNetwork toggles isPrivateIP guard), Android/iOS platform bridge, Dart settings model/provider, and a toggle in Download settings.
This commit is contained in:
@@ -2297,6 +2297,13 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"setAllowPrivateNetwork" -> {
|
||||
val allowed = call.argument<Boolean>("allowed") ?: false
|
||||
withContext(Dispatchers.IO) {
|
||||
Gobackend.setAllowPrivateNetwork(allowed)
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"checkDuplicate" -> {
|
||||
val outputDir = call.argument<String>("output_dir") ?: ""
|
||||
val isrc = call.argument<String>("isrc") ?: ""
|
||||
|
||||
@@ -8,11 +8,35 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
// allowPrivateNetworkAccess, when enabled, disables the SSRF guard that blocks
|
||||
// requests resolving to private/local/loopback addresses. This is opt-in and
|
||||
// intended for users who route the app's traffic through a local proxy or
|
||||
// custom DNS (e.g. a local mirror of api.zarz.moe). Disabled by default.
|
||||
var allowPrivateNetworkAccess atomic.Bool
|
||||
|
||||
// SetAllowPrivateNetwork toggles whether extensions and built-in network code
|
||||
// are permitted to reach private/local network targets. Exposed to the Flutter
|
||||
// layer via the platform bridge.
|
||||
func SetAllowPrivateNetwork(allowed bool) {
|
||||
allowPrivateNetworkAccess.Store(allowed)
|
||||
if allowed {
|
||||
GoLog("[HTTP] Private/local network access ENABLED (SSRF guard relaxed)\n")
|
||||
} else {
|
||||
GoLog("[HTTP] Private/local network access disabled (default)\n")
|
||||
}
|
||||
}
|
||||
|
||||
// IsPrivateNetworkAllowed reports the current state of the private-network guard.
|
||||
func IsPrivateNetworkAllowed() bool {
|
||||
return allowPrivateNetworkAccess.Load()
|
||||
}
|
||||
|
||||
const DefaultJSTimeout = 30 * time.Second
|
||||
|
||||
var (
|
||||
@@ -303,6 +327,12 @@ func (e *RedirectBlockedError) Error() string {
|
||||
}
|
||||
|
||||
func isPrivateIP(host string) bool {
|
||||
// Opt-in escape hatch: when the user has enabled private/local network
|
||||
// access, treat every host as public so local proxies / custom DNS work.
|
||||
if allowPrivateNetworkAccess.Load() {
|
||||
return false
|
||||
}
|
||||
|
||||
hostLower := strings.ToLower(strings.TrimSpace(host))
|
||||
if hostLower == "" {
|
||||
return false
|
||||
|
||||
@@ -393,6 +393,12 @@ import Gobackend // Import Go framework
|
||||
let insecureTLS = args["insecure_tls"] as? Bool ?? false
|
||||
GobackendSetNetworkCompatibilityOptions(allowHTTP, insecureTLS)
|
||||
return nil
|
||||
|
||||
case "setAllowPrivateNetwork":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let allowed = args["allowed"] as? Bool ?? false
|
||||
GobackendSetAllowPrivateNetwork(allowed)
|
||||
return nil
|
||||
|
||||
case "checkDuplicate":
|
||||
let args = call.arguments as! [String: Any]
|
||||
|
||||
@@ -55,6 +55,8 @@ class AppSettings {
|
||||
downloadNetworkMode; // 'any' = WiFi + Mobile, 'wifi_only' = WiFi only
|
||||
final bool
|
||||
networkCompatibilityMode; // Try HTTP + allow invalid TLS cert for API requests
|
||||
final bool
|
||||
allowLocalNetwork; // Allow requests to private/local network targets (local proxy / custom DNS)
|
||||
final String
|
||||
songLinkRegion; // SongLink userCountry region code used for platform lookup
|
||||
final bool
|
||||
@@ -135,6 +137,7 @@ class AppSettings {
|
||||
this.autoExportFailedDownloads = false,
|
||||
this.downloadNetworkMode = 'any',
|
||||
this.networkCompatibilityMode = false,
|
||||
this.allowLocalNetwork = false,
|
||||
this.songLinkRegion = 'US',
|
||||
this.nativeDownloadWorkerEnabled = false,
|
||||
this.localLibraryEnabled = false,
|
||||
@@ -200,6 +203,7 @@ class AppSettings {
|
||||
bool? autoExportFailedDownloads,
|
||||
String? downloadNetworkMode,
|
||||
bool? networkCompatibilityMode,
|
||||
bool? allowLocalNetwork,
|
||||
String? songLinkRegion,
|
||||
bool? nativeDownloadWorkerEnabled,
|
||||
bool? localLibraryEnabled,
|
||||
@@ -275,6 +279,7 @@ class AppSettings {
|
||||
downloadNetworkMode: downloadNetworkMode ?? this.downloadNetworkMode,
|
||||
networkCompatibilityMode:
|
||||
networkCompatibilityMode ?? this.networkCompatibilityMode,
|
||||
allowLocalNetwork: allowLocalNetwork ?? this.allowLocalNetwork,
|
||||
songLinkRegion: songLinkRegion ?? this.songLinkRegion,
|
||||
nativeDownloadWorkerEnabled:
|
||||
nativeDownloadWorkerEnabled ?? this.nativeDownloadWorkerEnabled,
|
||||
|
||||
@@ -56,6 +56,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
|
||||
json['autoExportFailedDownloads'] as bool? ?? false,
|
||||
downloadNetworkMode: json['downloadNetworkMode'] as String? ?? 'any',
|
||||
networkCompatibilityMode: json['networkCompatibilityMode'] as bool? ?? false,
|
||||
allowLocalNetwork: json['allowLocalNetwork'] as bool? ?? false,
|
||||
songLinkRegion: json['songLinkRegion'] as String? ?? 'US',
|
||||
nativeDownloadWorkerEnabled:
|
||||
json['nativeDownloadWorkerEnabled'] as bool? ?? false,
|
||||
@@ -130,6 +131,7 @@ Map<String, dynamic> _$AppSettingsToJson(
|
||||
'autoExportFailedDownloads': instance.autoExportFailedDownloads,
|
||||
'downloadNetworkMode': instance.downloadNetworkMode,
|
||||
'networkCompatibilityMode': instance.networkCompatibilityMode,
|
||||
'allowLocalNetwork': instance.allowLocalNetwork,
|
||||
'songLinkRegion': instance.songLinkRegion,
|
||||
'nativeDownloadWorkerEnabled': instance.nativeDownloadWorkerEnabled,
|
||||
'localLibraryEnabled': instance.localLibraryEnabled,
|
||||
|
||||
@@ -125,6 +125,12 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
).catchError((Object e) {
|
||||
_log.w('Failed to sync network compatibility options to backend: $e');
|
||||
});
|
||||
|
||||
PlatformBridge.setAllowPrivateNetwork(state.allowLocalNetwork).catchError((
|
||||
Object e,
|
||||
) {
|
||||
_log.w('Failed to sync allow local network option to backend: $e');
|
||||
});
|
||||
}
|
||||
|
||||
void _syncExtensionFallbackSettingsToBackend() {
|
||||
@@ -575,6 +581,12 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
_syncNetworkCompatibilitySettingsToBackend();
|
||||
}
|
||||
|
||||
void setAllowLocalNetwork(bool enabled) {
|
||||
state = state.copyWith(allowLocalNetwork: enabled);
|
||||
_saveSettings();
|
||||
_syncNetworkCompatibilitySettingsToBackend();
|
||||
}
|
||||
|
||||
void setSongLinkRegion(String region) {
|
||||
final normalized = _normalizeSongLinkRegion(region);
|
||||
state = state.copyWith(songLinkRegion: normalized);
|
||||
|
||||
@@ -221,6 +221,17 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
onChanged: (value) => ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setNetworkCompatibilityMode(value),
|
||||
),
|
||||
SettingsSwitchItem(
|
||||
icon: Icons.lan_outlined,
|
||||
title: context.l10n.downloadAllowLocalNetwork,
|
||||
subtitle: settings.allowLocalNetwork
|
||||
? context.l10n.downloadAllowLocalNetworkEnabled
|
||||
: context.l10n.downloadAllowLocalNetworkDisabled,
|
||||
value: settings.allowLocalNetwork,
|
||||
onChanged: (value) => ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setAllowLocalNetwork(value),
|
||||
showDivider: false,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -546,6 +546,12 @@ class PlatformBridge {
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> setAllowPrivateNetwork(bool allowed) async {
|
||||
await _channel.invokeMethod('setAllowPrivateNetwork', {
|
||||
'allowed': allowed,
|
||||
});
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> checkDuplicate(
|
||||
String outputDir,
|
||||
String isrc,
|
||||
|
||||
Reference in New Issue
Block a user