mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Plumbing for setting domain network policy (#20218)
This commit is contained in:
parent
4746a93ae8
commit
dd2c5a1b8b
@ -729,6 +729,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/Flutte
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterApplicationInfo.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java
|
||||
|
||||
@ -109,8 +109,10 @@ struct Settings {
|
||||
bool enable_dart_profiling = false;
|
||||
bool disable_dart_asserts = false;
|
||||
|
||||
// Used to signal the embedder whether HTTP connections are disabled.
|
||||
bool disable_http = false;
|
||||
// Whether embedder only allows secure connections.
|
||||
bool may_insecurely_connect_to_all_domains = true;
|
||||
// JSON-formatted domain network policy.
|
||||
std::string domain_network_policy;
|
||||
|
||||
// Used as the script URI in debug messages. Does not affect how the Dart code
|
||||
// is executed.
|
||||
|
||||
@ -16,19 +16,27 @@ using tonic::ToDart;
|
||||
|
||||
namespace flutter {
|
||||
|
||||
void DartIO::InitForIsolate(bool disable_http) {
|
||||
Dart_Handle result = Dart_SetNativeResolver(
|
||||
Dart_LookupLibrary(ToDart("dart:io")), dart::bin::LookupIONative,
|
||||
dart::bin::LookupIONativeSymbol);
|
||||
void DartIO::InitForIsolate(bool may_insecurely_connect_to_all_domains,
|
||||
std::string domain_network_policy) {
|
||||
Dart_Handle io_lib = Dart_LookupLibrary(ToDart("dart:io"));
|
||||
Dart_Handle result = Dart_SetNativeResolver(io_lib, dart::bin::LookupIONative,
|
||||
dart::bin::LookupIONativeSymbol);
|
||||
FML_CHECK(!LogIfError(result));
|
||||
|
||||
// The SDK expects this field to represent "allow http" so we switch the
|
||||
// value.
|
||||
Dart_Handle allow_http_value = disable_http ? Dart_False() : Dart_True();
|
||||
Dart_Handle set_field_result =
|
||||
Dart_SetField(Dart_LookupLibrary(ToDart("dart:_http")),
|
||||
ToDart("_embedderAllowsHttp"), allow_http_value);
|
||||
FML_CHECK(!LogIfError(set_field_result));
|
||||
Dart_Handle embedder_config_type =
|
||||
Dart_GetType(io_lib, ToDart("_EmbedderConfig"), 0, nullptr);
|
||||
FML_CHECK(!LogIfError(embedder_config_type));
|
||||
|
||||
Dart_Handle allow_insecure_connections_result = Dart_SetField(
|
||||
embedder_config_type, ToDart("_mayInsecurelyConnectToAllDomains"),
|
||||
ToDart(may_insecurely_connect_to_all_domains));
|
||||
FML_CHECK(!LogIfError(allow_insecure_connections_result));
|
||||
|
||||
Dart_Handle dart_args[1];
|
||||
dart_args[0] = ToDart(domain_network_policy);
|
||||
Dart_Handle set_domain_network_policy_result = Dart_Invoke(
|
||||
embedder_config_type, ToDart("_setDomainPolicies"), 1, dart_args);
|
||||
FML_CHECK(!LogIfError(set_domain_network_policy_result));
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#define FLUTTER_LIB_IO_DART_IO_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
|
||||
@ -13,7 +14,8 @@ namespace flutter {
|
||||
|
||||
class DartIO {
|
||||
public:
|
||||
static void InitForIsolate(bool disable_http);
|
||||
static void InitForIsolate(bool may_insecurely_connect_to_all_domains,
|
||||
std::string domain_network_policy);
|
||||
|
||||
private:
|
||||
FML_DISALLOW_IMPLICIT_CONSTRUCTORS(DartIO);
|
||||
|
||||
@ -139,7 +139,9 @@ DartIsolate::DartIsolate(const Settings& settings,
|
||||
settings.unhandled_exception_callback,
|
||||
DartVMRef::GetIsolateNameServer(),
|
||||
is_root_isolate),
|
||||
disable_http_(settings.disable_http) {
|
||||
may_insecurely_connect_to_all_domains_(
|
||||
settings.may_insecurely_connect_to_all_domains),
|
||||
domain_network_policy_(settings.domain_network_policy) {
|
||||
phase_ = Phase::Uninitialized;
|
||||
}
|
||||
|
||||
@ -263,7 +265,8 @@ bool DartIsolate::LoadLibraries() {
|
||||
|
||||
tonic::DartState::Scope scope(this);
|
||||
|
||||
DartIO::InitForIsolate(disable_http_);
|
||||
DartIO::InitForIsolate(may_insecurely_connect_to_all_domains_,
|
||||
domain_network_policy_);
|
||||
|
||||
DartUI::InitForIsolate();
|
||||
|
||||
|
||||
@ -398,7 +398,8 @@ class DartIsolate : public UIDartState {
|
||||
std::vector<std::shared_ptr<const fml::Mapping>> kernel_buffers_;
|
||||
std::vector<std::unique_ptr<AutoFireClosure>> shutdown_callbacks_;
|
||||
fml::RefPtr<fml::TaskRunner> message_handling_task_runner_;
|
||||
const bool disable_http_;
|
||||
const bool may_insecurely_connect_to_all_domains_;
|
||||
std::string domain_network_policy_;
|
||||
|
||||
DartIsolate(const Settings& settings,
|
||||
TaskRunners task_runners,
|
||||
|
||||
@ -242,8 +242,11 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) {
|
||||
}
|
||||
}
|
||||
|
||||
settings.disable_http =
|
||||
command_line.HasOption(FlagForSwitch(Switch::DisableHttp));
|
||||
settings.may_insecurely_connect_to_all_domains = !command_line.HasOption(
|
||||
FlagForSwitch(Switch::DisallowInsecureConnections));
|
||||
|
||||
command_line.GetOptionValue(FlagForSwitch(Switch::DomainNetworkPolicy),
|
||||
&settings.domain_network_policy);
|
||||
|
||||
// Disable need for authentication codes for VM service communication, if
|
||||
// specified.
|
||||
|
||||
@ -181,12 +181,15 @@ DEF_SWITCH(DisableDartAsserts,
|
||||
"disabled. This flag may be specified if the user wishes to run "
|
||||
"with assertions disabled in the debug product mode (i.e. with JIT "
|
||||
"or DBC).")
|
||||
DEF_SWITCH(DisableHttp,
|
||||
"disable-http",
|
||||
"Dart VM has a master switch that can be set to disable insecure "
|
||||
"HTTP and WebSocket protocols. Localhost or loopback addresses are "
|
||||
"exempted. This flag can be specified if the embedder wants this "
|
||||
"for a particular platform.")
|
||||
DEF_SWITCH(DisallowInsecureConnections,
|
||||
"disallow-insecure-connections",
|
||||
"By default, dart:io allows all socket connections. If this switch "
|
||||
"is set, all insecure connections are rejected.")
|
||||
DEF_SWITCH(DomainNetworkPolicy,
|
||||
"domain-network-policy",
|
||||
"JSON encoded network policy per domain. This overrides the "
|
||||
"DisallowInsecureConnections switch. Embedder can specify whether "
|
||||
"to allow or disallow insecure connections at a domain level.")
|
||||
DEF_SWITCH(
|
||||
ForceMultithreading,
|
||||
"force-multithreading",
|
||||
|
||||
@ -153,6 +153,8 @@ android_java_sources = [
|
||||
"io/flutter/embedding/engine/dart/DartExecutor.java",
|
||||
"io/flutter/embedding/engine/dart/DartMessenger.java",
|
||||
"io/flutter/embedding/engine/dart/PlatformMessageHandler.java",
|
||||
"io/flutter/embedding/engine/loader/ApplicationInfoLoader.java",
|
||||
"io/flutter/embedding/engine/loader/FlutterApplicationInfo.java",
|
||||
"io/flutter/embedding/engine/loader/FlutterLoader.java",
|
||||
"io/flutter/embedding/engine/loader/ResourceExtractor.java",
|
||||
"io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java",
|
||||
@ -430,6 +432,7 @@ action("robolectric_tests") {
|
||||
"test/io/flutter/embedding/engine/PluginComponentTest.java",
|
||||
"test/io/flutter/embedding/engine/RenderingComponentTest.java",
|
||||
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
|
||||
"test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java",
|
||||
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",
|
||||
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
|
||||
"test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java",
|
||||
|
||||
@ -0,0 +1,161 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.embedding.engine.loader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.os.Bundle;
|
||||
import android.security.NetworkSecurityPolicy;
|
||||
import androidx.annotation.NonNull;
|
||||
import java.io.IOException;
|
||||
import org.json.JSONArray;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
/** Loads application information given a Context. */
|
||||
final class ApplicationInfoLoader {
|
||||
// XML Attribute keys supported in AndroidManifest.xml
|
||||
static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
|
||||
FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME;
|
||||
static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
|
||||
FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY;
|
||||
static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
|
||||
FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY;
|
||||
static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
|
||||
FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY;
|
||||
static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";
|
||||
|
||||
@NonNull
|
||||
private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
|
||||
try {
|
||||
return applicationContext
|
||||
.getPackageManager()
|
||||
.getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getString(Bundle metadata, String key) {
|
||||
if (metadata == null) {
|
||||
return null;
|
||||
}
|
||||
return metadata.getString(key, null);
|
||||
}
|
||||
|
||||
private static String getNetworkPolicy(ApplicationInfo appInfo, Context context) {
|
||||
// We cannot use reflection to look at networkSecurityConfigRes because
|
||||
// Android throws an error when we try to access fields marked as @hide.
|
||||
// Instead we rely on metadata.
|
||||
Bundle metadata = appInfo.metaData;
|
||||
if (metadata == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int networkSecurityConfigRes = metadata.getInt(NETWORK_POLICY_METADATA_KEY, 0);
|
||||
if (networkSecurityConfigRes <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONArray output = new JSONArray();
|
||||
try {
|
||||
XmlResourceParser xrp = context.getResources().getXml(networkSecurityConfigRes);
|
||||
xrp.next();
|
||||
int eventType = xrp.getEventType();
|
||||
while (eventType != XmlResourceParser.END_DOCUMENT) {
|
||||
if (eventType == XmlResourceParser.START_TAG) {
|
||||
if (xrp.getName().equals("domain-config")) {
|
||||
parseDomainConfig(xrp, output, false);
|
||||
}
|
||||
}
|
||||
eventType = xrp.next();
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
return null;
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private static boolean getUseEmbeddedView(ApplicationInfo appInfo) {
|
||||
Bundle bundle = appInfo.metaData;
|
||||
return bundle != null && bundle.getBoolean("io.flutter.embedded_views_preview");
|
||||
}
|
||||
|
||||
private static void parseDomainConfig(
|
||||
XmlResourceParser xrp, JSONArray output, boolean inheritedCleartextPermitted)
|
||||
throws IOException, XmlPullParserException {
|
||||
boolean cleartextTrafficPermitted =
|
||||
xrp.getAttributeBooleanValue(
|
||||
null, "cleartextTrafficPermitted", inheritedCleartextPermitted);
|
||||
while (true) {
|
||||
int eventType = xrp.next();
|
||||
if (eventType == XmlResourceParser.START_TAG) {
|
||||
if (xrp.getName().equals("domain")) {
|
||||
// There can be multiple domains.
|
||||
parseDomain(xrp, output, cleartextTrafficPermitted);
|
||||
} else if (xrp.getName().equals("domain-config")) {
|
||||
parseDomainConfig(xrp, output, cleartextTrafficPermitted);
|
||||
} else {
|
||||
skipTag(xrp);
|
||||
}
|
||||
} else if (eventType == XmlResourceParser.END_TAG) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void skipTag(XmlResourceParser xrp) throws IOException, XmlPullParserException {
|
||||
String name = xrp.getName();
|
||||
int eventType = xrp.getEventType();
|
||||
while (eventType != XmlResourceParser.END_TAG || xrp.getName() != name) {
|
||||
eventType = xrp.next();
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseDomain(
|
||||
XmlResourceParser xrp, JSONArray output, boolean cleartextPermitted)
|
||||
throws IOException, XmlPullParserException {
|
||||
boolean includeSubDomains = xrp.getAttributeBooleanValue(null, "includeSubdomains", false);
|
||||
xrp.next();
|
||||
if (xrp.getEventType() != XmlResourceParser.TEXT) {
|
||||
throw new IllegalStateException("Expected text");
|
||||
}
|
||||
String domain = xrp.getText().trim();
|
||||
JSONArray outputArray = new JSONArray();
|
||||
outputArray.put(domain);
|
||||
outputArray.put(includeSubDomains);
|
||||
outputArray.put(cleartextPermitted);
|
||||
output.put(outputArray);
|
||||
xrp.next();
|
||||
if (xrp.getEventType() != XmlResourceParser.END_TAG) {
|
||||
throw new IllegalStateException("Expected end of domain tag");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize our Flutter config values by obtaining them from the manifest XML file, falling back
|
||||
* to default values.
|
||||
*/
|
||||
@NonNull
|
||||
public static FlutterApplicationInfo load(@NonNull Context applicationContext) {
|
||||
ApplicationInfo appInfo = getApplicationInfo(applicationContext);
|
||||
// Prior to API 23, cleartext traffic is allowed.
|
||||
boolean clearTextPermitted = true;
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
clearTextPermitted = NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
|
||||
}
|
||||
|
||||
return new FlutterApplicationInfo(
|
||||
getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME),
|
||||
getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY),
|
||||
getString(appInfo.metaData, PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY),
|
||||
getString(appInfo.metaData, PUBLIC_FLUTTER_ASSETS_DIR_KEY),
|
||||
getNetworkPolicy(appInfo, applicationContext),
|
||||
appInfo.nativeLibraryDir,
|
||||
clearTextPermitted,
|
||||
getUseEmbeddedView(appInfo));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.embedding.engine.loader;
|
||||
|
||||
/** Encapsulates all the information that Flutter needs from application manifest. */
|
||||
public final class FlutterApplicationInfo {
|
||||
private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
|
||||
private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
|
||||
private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
|
||||
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
|
||||
|
||||
final String aotSharedLibraryName;
|
||||
final String vmSnapshotData;
|
||||
final String isolateSnapshotData;
|
||||
final String flutterAssetsDir;
|
||||
final String domainNetworkPolicy;
|
||||
final String nativeLibraryDir;
|
||||
final boolean clearTextPermitted;
|
||||
// TODO(cyanlaz): Remove this when dynamic thread merging is done.
|
||||
// https://github.com/flutter/flutter/issues/59930
|
||||
final boolean useEmbeddedView;
|
||||
|
||||
public FlutterApplicationInfo(
|
||||
String aotSharedLibraryName,
|
||||
String vmSnapshotData,
|
||||
String isolateSnapshotData,
|
||||
String flutterAssetsDir,
|
||||
String domainNetworkPolicy,
|
||||
String nativeLibraryDir,
|
||||
boolean clearTextPermitted,
|
||||
boolean useEmbeddedView) {
|
||||
this.aotSharedLibraryName =
|
||||
aotSharedLibraryName == null ? DEFAULT_AOT_SHARED_LIBRARY_NAME : aotSharedLibraryName;
|
||||
this.vmSnapshotData = vmSnapshotData == null ? DEFAULT_VM_SNAPSHOT_DATA : vmSnapshotData;
|
||||
this.isolateSnapshotData =
|
||||
isolateSnapshotData == null ? DEFAULT_ISOLATE_SNAPSHOT_DATA : isolateSnapshotData;
|
||||
this.flutterAssetsDir =
|
||||
flutterAssetsDir == null ? DEFAULT_FLUTTER_ASSETS_DIR : flutterAssetsDir;
|
||||
this.nativeLibraryDir = nativeLibraryDir;
|
||||
this.domainNetworkPolicy = domainNetworkPolicy == null ? "" : domainNetworkPolicy;
|
||||
this.clearTextPermitted = clearTextPermitted;
|
||||
this.useEmbeddedView = useEmbeddedView;
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,8 @@
|
||||
package io.flutter.embedding.engine.loader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
@ -31,35 +29,15 @@ public class FlutterLoader {
|
||||
private static final String TAG = "FlutterLoader";
|
||||
|
||||
// Must match values in flutter::switches
|
||||
private static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
|
||||
private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
|
||||
private static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
|
||||
private static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
|
||||
private static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
|
||||
|
||||
// XML Attribute keys supported in AndroidManifest.xml
|
||||
private static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
|
||||
FlutterLoader.class.getName() + '.' + AOT_SHARED_LIBRARY_NAME;
|
||||
private static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
|
||||
FlutterLoader.class.getName() + '.' + VM_SNAPSHOT_DATA_KEY;
|
||||
private static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
|
||||
FlutterLoader.class.getName() + '.' + ISOLATE_SNAPSHOT_DATA_KEY;
|
||||
private static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
|
||||
FlutterLoader.class.getName() + '.' + FLUTTER_ASSETS_DIR_KEY;
|
||||
static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
|
||||
static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
|
||||
static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
|
||||
static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
|
||||
static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
|
||||
|
||||
// Resource names used for components of the precompiled snapshot.
|
||||
private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
|
||||
private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
|
||||
private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
|
||||
private static final String DEFAULT_LIBRARY = "libflutter.so";
|
||||
private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
|
||||
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
|
||||
|
||||
// Mutable because default values can be overridden via config properties
|
||||
private String aotSharedLibraryName = DEFAULT_AOT_SHARED_LIBRARY_NAME;
|
||||
private String vmSnapshotData = DEFAULT_VM_SNAPSHOT_DATA;
|
||||
private String isolateSnapshotData = DEFAULT_ISOLATE_SNAPSHOT_DATA;
|
||||
private String flutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR;
|
||||
|
||||
private static FlutterLoader instance;
|
||||
|
||||
@ -78,9 +56,17 @@ public class FlutterLoader {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FlutterLoader getInstanceForTest(FlutterApplicationInfo flutterApplicationInfo) {
|
||||
FlutterLoader loader = new FlutterLoader();
|
||||
loader.flutterApplicationInfo = flutterApplicationInfo;
|
||||
return loader;
|
||||
}
|
||||
|
||||
private boolean initialized = false;
|
||||
@Nullable private Settings settings;
|
||||
private long initStartTimestampMillis;
|
||||
private FlutterApplicationInfo flutterApplicationInfo;
|
||||
|
||||
private static class InitResult {
|
||||
final String appStoragePath;
|
||||
@ -131,7 +117,7 @@ public class FlutterLoader {
|
||||
this.settings = settings;
|
||||
|
||||
initStartTimestampMillis = SystemClock.uptimeMillis();
|
||||
initConfig(appContext);
|
||||
flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
|
||||
VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
|
||||
.init();
|
||||
|
||||
@ -195,10 +181,9 @@ public class FlutterLoader {
|
||||
List<String> shellArgs = new ArrayList<>();
|
||||
shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
|
||||
|
||||
ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
|
||||
shellArgs.add(
|
||||
"--icu-native-lib-path="
|
||||
+ applicationInfo.nativeLibraryDir
|
||||
+ flutterApplicationInfo.nativeLibraryDir
|
||||
+ File.separator
|
||||
+ DEFAULT_LIBRARY);
|
||||
if (args != null) {
|
||||
@ -207,13 +192,16 @@ public class FlutterLoader {
|
||||
|
||||
String kernelPath = null;
|
||||
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
|
||||
String snapshotAssetPath = result.dataDirPath + File.separator + flutterAssetsDir;
|
||||
String snapshotAssetPath =
|
||||
result.dataDirPath + File.separator + flutterApplicationInfo.flutterAssetsDir;
|
||||
kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
|
||||
shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
|
||||
shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + vmSnapshotData);
|
||||
shellArgs.add("--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + isolateSnapshotData);
|
||||
shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.vmSnapshotData);
|
||||
shellArgs.add(
|
||||
"--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData);
|
||||
} else {
|
||||
shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryName);
|
||||
shellArgs.add(
|
||||
"--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);
|
||||
|
||||
// Most devices can load the AOT shared library based on the library name
|
||||
// with no directory path. Provide a fully qualified path to the library
|
||||
@ -222,28 +210,28 @@ public class FlutterLoader {
|
||||
"--"
|
||||
+ AOT_SHARED_LIBRARY_NAME
|
||||
+ "="
|
||||
+ applicationInfo.nativeLibraryDir
|
||||
+ flutterApplicationInfo.nativeLibraryDir
|
||||
+ File.separator
|
||||
+ aotSharedLibraryName);
|
||||
+ flutterApplicationInfo.aotSharedLibraryName);
|
||||
}
|
||||
|
||||
shellArgs.add("--cache-dir-path=" + result.engineCachesPath);
|
||||
// TODO(mehmetf): Announce this since it is a breaking change then enable it.
|
||||
// if (!flutterApplicationInfo.clearTextPermitted) {
|
||||
// shellArgs.add("--disallow-insecure-connections");
|
||||
// }
|
||||
if (flutterApplicationInfo.domainNetworkPolicy != null) {
|
||||
shellArgs.add("--domain-network-policy=" + flutterApplicationInfo.domainNetworkPolicy);
|
||||
}
|
||||
if (flutterApplicationInfo.useEmbeddedView) {
|
||||
shellArgs.add("--use-embedded-view");
|
||||
}
|
||||
if (settings.getLogTag() != null) {
|
||||
shellArgs.add("--log-tag=" + settings.getLogTag());
|
||||
}
|
||||
|
||||
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
|
||||
|
||||
// TODO(cyanlaz): Remove this when dynamic thread merging is done.
|
||||
// https://github.com/flutter/flutter/issues/59930
|
||||
Bundle bundle = applicationInfo.metaData;
|
||||
if (bundle != null) {
|
||||
boolean use_embedded_view = bundle.getBoolean("io.flutter.embedded_views_preview");
|
||||
if (use_embedded_view) {
|
||||
shellArgs.add("--use-embedded-view");
|
||||
}
|
||||
}
|
||||
|
||||
FlutterJNI.nativeInit(
|
||||
applicationContext,
|
||||
shellArgs.toArray(new String[0]),
|
||||
@ -306,40 +294,6 @@ public class FlutterLoader {
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
|
||||
try {
|
||||
return applicationContext
|
||||
.getPackageManager()
|
||||
.getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize our Flutter config values by obtaining them from the manifest XML file, falling back
|
||||
* to default values.
|
||||
*/
|
||||
private void initConfig(@NonNull Context applicationContext) {
|
||||
Bundle metadata = getApplicationInfo(applicationContext).metaData;
|
||||
|
||||
// There isn't a `<meta-data>` tag as a direct child of `<application>` in
|
||||
// `AndroidManifest.xml`.
|
||||
if (metadata == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
aotSharedLibraryName =
|
||||
metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, DEFAULT_AOT_SHARED_LIBRARY_NAME);
|
||||
flutterAssetsDir =
|
||||
metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR);
|
||||
|
||||
vmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, DEFAULT_VM_SNAPSHOT_DATA);
|
||||
isolateSnapshotData =
|
||||
metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_ISOLATE_SNAPSHOT_DATA);
|
||||
}
|
||||
|
||||
/** Extract assets out of the APK that need to be cached as uncompressed files on disk. */
|
||||
private ResourceExtractor initResources(@NonNull Context applicationContext) {
|
||||
ResourceExtractor resourceExtractor = null;
|
||||
@ -354,8 +308,8 @@ public class FlutterLoader {
|
||||
// In debug/JIT mode these assets will be written to disk and then
|
||||
// mapped into memory so they can be provided to the Dart VM.
|
||||
resourceExtractor
|
||||
.addResource(fullAssetPathFrom(vmSnapshotData))
|
||||
.addResource(fullAssetPathFrom(isolateSnapshotData))
|
||||
.addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData))
|
||||
.addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData))
|
||||
.addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
|
||||
|
||||
resourceExtractor.start();
|
||||
@ -365,7 +319,7 @@ public class FlutterLoader {
|
||||
|
||||
@NonNull
|
||||
public String findAppBundlePath() {
|
||||
return flutterAssetsDir;
|
||||
return flutterApplicationInfo.flutterAssetsDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -396,7 +350,7 @@ public class FlutterLoader {
|
||||
|
||||
@NonNull
|
||||
private String fullAssetPathFrom(@NonNull String filePath) {
|
||||
return flutterAssetsDir + File.separator + filePath;
|
||||
return flutterApplicationInfo.flutterAssetsDir + File.separator + filePath;
|
||||
}
|
||||
|
||||
public static class Settings {
|
||||
|
||||
@ -8,6 +8,7 @@ import static org.mockito.Mockito.when;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import io.flutter.embedding.engine.FlutterJNI;
|
||||
import io.flutter.embedding.engine.loader.FlutterApplicationInfo;
|
||||
import io.flutter.embedding.engine.loader.FlutterLoader;
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import org.junit.Test;
|
||||
@ -26,6 +27,8 @@ public class PluginComponentTest {
|
||||
// Setup test.
|
||||
FlutterJNI flutterJNI = mock(FlutterJNI.class);
|
||||
when(flutterJNI.isAttached()).thenReturn(true);
|
||||
FlutterApplicationInfo emptyInfo =
|
||||
new FlutterApplicationInfo(null, null, null, null, null, null, false, false);
|
||||
|
||||
// FlutterLoader is the object to which the PluginRegistry defers for obtaining
|
||||
// the path to a Flutter asset. Ideally in this component test we would use a
|
||||
@ -44,7 +47,8 @@ public class PluginComponentTest {
|
||||
public String answer(InvocationOnMock invocation) throws Throwable {
|
||||
// Defer to a real FlutterLoader to return the asset path.
|
||||
String fileNameOrSubpath = (String) invocation.getArguments()[0];
|
||||
return FlutterLoader.getInstance().getLookupKeyForAsset(fileNameOrSubpath);
|
||||
return FlutterLoader.getInstanceForTest(emptyInfo)
|
||||
.getLookupKeyForAsset(fileNameOrSubpath);
|
||||
}
|
||||
});
|
||||
when(flutterLoader.getLookupKeyForAsset(any(String.class), any(String.class)))
|
||||
@ -55,7 +59,7 @@ public class PluginComponentTest {
|
||||
// Defer to a real FlutterLoader to return the asset path.
|
||||
String fileNameOrSubpath = (String) invocation.getArguments()[0];
|
||||
String packageName = (String) invocation.getArguments()[1];
|
||||
return FlutterLoader.getInstance()
|
||||
return FlutterLoader.getInstanceForTest(emptyInfo)
|
||||
.getLookupKeyForAsset(fileNameOrSubpath, packageName);
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,193 @@
|
||||
package io.flutter.embedding.engine.loader;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.os.Bundle;
|
||||
import android.security.NetworkSecurityPolicy;
|
||||
import java.io.StringReader;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
@Config(manifest = Config.NONE)
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ApplicationInfoLoaderTest {
|
||||
|
||||
@Test
|
||||
public void itGeneratesCorrectApplicationInfoWithDefaultManifest() {
|
||||
FlutterApplicationInfo info = ApplicationInfoLoader.load(RuntimeEnvironment.application);
|
||||
assertNotNull(info);
|
||||
assertEquals("libapp.so", info.aotSharedLibraryName);
|
||||
assertEquals("vm_snapshot_data", info.vmSnapshotData);
|
||||
assertEquals("isolate_snapshot_data", info.isolateSnapshotData);
|
||||
assertEquals("flutter_assets", info.flutterAssetsDir);
|
||||
assertEquals("", info.domainNetworkPolicy);
|
||||
assertNull(info.nativeLibraryDir);
|
||||
assertEquals(true, info.clearTextPermitted);
|
||||
assertEquals(false, info.useEmbeddedView);
|
||||
}
|
||||
|
||||
@Config(shadows = {ApplicationInfoLoaderTest.ShadowNetworkSecurityPolicy.class})
|
||||
@Test
|
||||
public void itVotesAgainstClearTextIfSecurityPolicySaysSo() {
|
||||
FlutterApplicationInfo info = ApplicationInfoLoader.load(RuntimeEnvironment.application);
|
||||
assertNotNull(info);
|
||||
assertEquals(false, info.clearTextPermitted);
|
||||
}
|
||||
|
||||
@Implements(NetworkSecurityPolicy.class)
|
||||
public static class ShadowNetworkSecurityPolicy {
|
||||
@Implementation
|
||||
public boolean isCleartextTrafficPermitted() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Context generateMockContext(Bundle metadata, String networkPolicyXml) throws Exception {
|
||||
Context context = mock(Context.class);
|
||||
PackageManager packageManager = mock(PackageManager.class);
|
||||
ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
|
||||
applicationInfo.metaData = metadata;
|
||||
Resources resources = mock(Resources.class);
|
||||
when(context.getPackageManager()).thenReturn(packageManager);
|
||||
when(context.getResources()).thenReturn(resources);
|
||||
when(packageManager.getApplicationInfo(any(String.class), any(int.class)))
|
||||
.thenReturn(applicationInfo);
|
||||
if (networkPolicyXml != null) {
|
||||
metadata.putInt(ApplicationInfoLoader.NETWORK_POLICY_METADATA_KEY, 5);
|
||||
doAnswer(invocationOnMock -> createMockResourceParser(networkPolicyXml))
|
||||
.when(resources)
|
||||
.getXml(5);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void itGeneratesCorrectApplicationInfoWithCustomValues() throws Exception {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "testaot");
|
||||
bundle.putString(ApplicationInfoLoader.PUBLIC_VM_SNAPSHOT_DATA_KEY, "testvmsnapshot");
|
||||
bundle.putString(ApplicationInfoLoader.PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, "testisolatesnapshot");
|
||||
bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "testassets");
|
||||
bundle.putBoolean("io.flutter.embedded_views_preview", true);
|
||||
Context context = generateMockContext(bundle, null);
|
||||
FlutterApplicationInfo info = ApplicationInfoLoader.load(context);
|
||||
assertNotNull(info);
|
||||
assertEquals("testaot", info.aotSharedLibraryName);
|
||||
assertEquals("testvmsnapshot", info.vmSnapshotData);
|
||||
assertEquals("testisolatesnapshot", info.isolateSnapshotData);
|
||||
assertEquals("testassets", info.flutterAssetsDir);
|
||||
assertNull(info.nativeLibraryDir);
|
||||
assertEquals("", info.domainNetworkPolicy);
|
||||
assertEquals(true, info.useEmbeddedView);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void itGeneratesCorrectNetworkPolicy() throws Exception {
|
||||
Bundle bundle = new Bundle();
|
||||
String networkPolicyXml =
|
||||
"<network-security-config>"
|
||||
+ "<domain-config cleartextTrafficPermitted=\"false\">"
|
||||
+ "<domain includeSubdomains=\"true\">secure.example.com</domain>"
|
||||
+ "</domain-config>"
|
||||
+ "</network-security-config>";
|
||||
Context context = generateMockContext(bundle, networkPolicyXml);
|
||||
FlutterApplicationInfo info = ApplicationInfoLoader.load(context);
|
||||
assertNotNull(info);
|
||||
assertEquals("[[\"secure.example.com\",true,false]]", info.domainNetworkPolicy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void itHandlesBogusInformationInNetworkPolicy() throws Exception {
|
||||
Bundle bundle = new Bundle();
|
||||
String networkPolicyXml =
|
||||
"<network-security-config>"
|
||||
+ "<domain-config cleartextTrafficPermitted=\"false\">"
|
||||
+ "<domain includeSubdomains=\"true\">secure.example.com</domain>"
|
||||
+ "<pin-set expiration=\"2018-01-01\">"
|
||||
+ "<pin digest=\"SHA-256\">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>"
|
||||
+ "<!-- backup pin -->"
|
||||
+ "<pin digest=\"SHA-256\">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>"
|
||||
+ "</pin-set>"
|
||||
+ "</domain-config>"
|
||||
+ "</network-security-config>";
|
||||
Context context = generateMockContext(bundle, networkPolicyXml);
|
||||
FlutterApplicationInfo info = ApplicationInfoLoader.load(context);
|
||||
assertNotNull(info);
|
||||
assertEquals("[[\"secure.example.com\",true,false]]", info.domainNetworkPolicy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void itHandlesNestedSubDomains() throws Exception {
|
||||
Bundle bundle = new Bundle();
|
||||
String networkPolicyXml =
|
||||
"<network-security-config>"
|
||||
+ "<domain-config cleartextTrafficPermitted=\"true\">"
|
||||
+ "<domain includeSubdomains=\"true\">example.com</domain>"
|
||||
+ "<domain-config>"
|
||||
+ "<domain includeSubdomains=\"true\">insecure.example.com</domain>"
|
||||
+ "</domain-config>"
|
||||
+ "<domain-config cleartextTrafficPermitted=\"false\">"
|
||||
+ "<domain includeSubdomains=\"true\">secure.example.com</domain>"
|
||||
+ "</domain-config>"
|
||||
+ "</domain-config>"
|
||||
+ "</network-security-config>";
|
||||
Context context = generateMockContext(bundle, networkPolicyXml);
|
||||
FlutterApplicationInfo info = ApplicationInfoLoader.load(context);
|
||||
assertNotNull(info);
|
||||
assertEquals(
|
||||
"[[\"example.com\",true,true],[\"insecure.example.com\",true,true],[\"secure.example.com\",true,false]]",
|
||||
info.domainNetworkPolicy);
|
||||
}
|
||||
|
||||
// The following ridiculousness is needed because Android gives no way for us
|
||||
// to customize XmlResourceParser. We have to mock it and tie each method
|
||||
// we use to an actual Xml parser.
|
||||
private XmlResourceParser createMockResourceParser(String xml) throws Exception {
|
||||
final XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
|
||||
xpp.setInput(new StringReader(xml));
|
||||
XmlResourceParser resourceParser = mock(XmlResourceParser.class);
|
||||
final Answer<Object> invokeMethodOnRealParser =
|
||||
invocation -> invocation.getMethod().invoke(xpp, invocation.getArguments());
|
||||
when(resourceParser.next()).thenAnswer(invokeMethodOnRealParser);
|
||||
when(resourceParser.getName()).thenAnswer(invokeMethodOnRealParser);
|
||||
when(resourceParser.getEventType()).thenAnswer(invokeMethodOnRealParser);
|
||||
when(resourceParser.getText()).thenAnswer(invokeMethodOnRealParser);
|
||||
when(resourceParser.getAttributeCount()).thenAnswer(invokeMethodOnRealParser);
|
||||
when(resourceParser.getAttributeName(anyInt())).thenAnswer(invokeMethodOnRealParser);
|
||||
when(resourceParser.getAttributeValue(anyInt())).thenAnswer(invokeMethodOnRealParser);
|
||||
when(resourceParser.getAttributeValue(any(String.class), any(String.class)))
|
||||
.thenAnswer(invokeMethodOnRealParser);
|
||||
when(resourceParser.getAttributeBooleanValue(
|
||||
any(String.class), any(String.class), any(Boolean.class)))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
Object[] args = invocation.getArguments();
|
||||
String result = xpp.getAttributeValue((String) args[0], (String) args[1]);
|
||||
if (result == null) {
|
||||
return (Boolean) args[2];
|
||||
}
|
||||
return Boolean.parseBoolean(result);
|
||||
});
|
||||
return resourceParser;
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,42 @@ extern const intptr_t kPlatformStrongDillSize;
|
||||
|
||||
static const char* kApplicationKernelSnapshotFileName = "kernel_blob.bin";
|
||||
|
||||
// TODO(mehmetf): Announce this since it is breaking change then enable it.
|
||||
// static NSString* DomainNetworkPolicy(NSDictionary* appTransportSecurity) {
|
||||
// if (appTransportSecurity == nil) {
|
||||
// return @"";
|
||||
// }
|
||||
// //
|
||||
// https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsexceptiondomains
|
||||
// NSDictionary* exceptionDomains = [appTransportSecurity objectForKey:@"NSExceptionDomains"];
|
||||
// if (exceptionDomains == nil) {
|
||||
// return @"";
|
||||
// }
|
||||
// NSMutableArray* networkConfigArray = [[NSMutableArray alloc] init];
|
||||
// for (NSString* domain in exceptionDomains) {
|
||||
// NSDictionary* domainConfiguration = [exceptionDomains objectForKey:domain];
|
||||
// BOOL includesSubDomains =
|
||||
// [[domainConfiguration objectForKey:@"NSIncludesSubdomains"] boolValue];
|
||||
// BOOL allowsCleartextCommunication =
|
||||
// [[domainConfiguration objectForKey:@"NSExceptionAllowsInsecureHTTPLoads"] boolValue];
|
||||
// [networkConfigArray addObject:[NSArray arrayWithObjects:domain, includesSubDomains,
|
||||
// allowsCleartextCommunication, nil]];
|
||||
// }
|
||||
// NSData* jsonData = [NSJSONSerialization dataWithJSONObject:networkConfigArray
|
||||
// options:0
|
||||
// error:NULL];
|
||||
// return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
// }
|
||||
|
||||
// TODO(mehmetf): Announce this since it is breaking change then enable it.
|
||||
// static bool AllowsArbitraryLoads(NSDictionary* appTransportSecurity) {
|
||||
// if (appTransportSecurity != nil) {
|
||||
// return [[appTransportSecurity objectForKey:@"NSAllowsArbitraryLoads"] boolValue];
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
static flutter::Settings DefaultSettingsForProcess(NSBundle* bundle = nil) {
|
||||
auto command_line = flutter::CommandLineFromNSProcessInfo();
|
||||
|
||||
@ -132,6 +168,13 @@ static flutter::Settings DefaultSettingsForProcess(NSBundle* bundle = nil) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mehmetf): Announce this since it is breaking change then enable it.
|
||||
// Domain network configuration
|
||||
// NSDictionary* appTransportSecurity =
|
||||
// [mainBundle objectForInfoDictionaryKey:@"NSAppTransportSecurity"];
|
||||
// settings.may_insecurely_connect_to_all_domains = AllowsArbitraryLoads(appTransportSecurity);
|
||||
// settings.domain_network_policy = DomainNetworkPolicy(appTransportSecurity).UTF8String;
|
||||
|
||||
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
|
||||
// There are no ownership concerns here as all mappings are owned by the
|
||||
// embedder and not the engine.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user