未验证 提交 dd2c5a1b 编写于 作者: M Mehmet Fidanboylu 提交者: GitHub

Plumbing for setting domain network policy (#20218)

上级 4746a93a
......@@ -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",
......
// 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));
}
}
// 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);
}
});
......
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.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册