Merge pull request #6397 from atorralba/atorralba/android-intent-redirect-query

Java: Create new Android Intent Redirection query
This commit is contained in:
Tony Torralba
2021-11-04 10:42:59 +01:00
committed by GitHub
28 changed files with 1458 additions and 1744 deletions

View File

@@ -107,6 +107,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.spring.SpringBeans
private import semmle.code.java.frameworks.spring.SpringWebMultipart
private import semmle.code.java.frameworks.spring.SpringWebUtil
private import semmle.code.java.security.AndroidIntentRedirection
private import semmle.code.java.security.ResponseSplitting
private import semmle.code.java.security.InformationLeak
private import semmle.code.java.security.GroovyInjection

View File

@@ -10,6 +10,11 @@ class TypeIntent extends Class {
TypeIntent() { this.hasQualifiedName("android.content", "Intent") }
}
/** The class `android.content.ComponentName`. */
class TypeComponentName extends Class {
TypeComponentName() { this.hasQualifiedName("android.content", "ComponentName") }
}
/**
* The class `android.app.Activity`.
*/
@@ -293,3 +298,34 @@ private class IntentBundleFlowSteps extends SummaryModelCsv {
]
}
}
private class IntentComponentTaintSteps extends SummaryModelCsv {
override predicate row(string s) {
s =
[
"android.content;Intent;true;Intent;(Intent);;Argument[0];Argument[-1];taint",
"android.content;Intent;true;Intent;(Context,Class);;Argument[1];Argument[-1];taint",
"android.content;Intent;true;Intent;(String,Uri,Context,Class);;Argument[3];Argument[-1];taint",
"android.content;Intent;true;getIntent;(String);;Argument[0];ReturnValue;taint",
"android.content;Intent;true;getIntentOld;(String);;Argument[0];ReturnValue;taint",
"android.content;Intent;true;parseUri;(String,int);;Argument[0];ReturnValue;taint",
"android.content;Intent;true;setPackage;;;Argument[0];Argument[-1];taint",
"android.content;Intent;true;setClass;;;Argument[1];Argument[-1];taint",
"android.content;Intent;true;setClassName;(Context,String);;Argument[1];Argument[-1];taint",
"android.content;Intent;true;setClassName;(String,String);;Argument[0..1];Argument[-1];taint",
"android.content;Intent;true;setComponent;;;Argument[0];Argument[-1];taint",
"android.content;ComponentName;false;ComponentName;(String,String);;Argument[0..1];Argument[-1];taint",
"android.content;ComponentName;false;ComponentName;(Context,String);;Argument[1];Argument[-1];taint",
"android.content;ComponentName;false;ComponentName;(Context,Class);;Argument[1];Argument[-1];taint",
"android.content;ComponentName;false;ComponentName;(Parcel);;Argument[0];Argument[-1];taint",
"android.content;ComponentName;false;createRelative;(String,String);;Argument[0..1];ReturnValue;taint",
"android.content;ComponentName;false;createRelative;(Context,String);;Argument[1];ReturnValue;taint",
"android.content;ComponentName;false;flattenToShortString;;;Argument[-1];ReturnValue;taint",
"android.content;ComponentName;false;flattenToString;;;Argument[-1];ReturnValue;taint",
"android.content;ComponentName;false;getClassName;;;Argument[-1];ReturnValue;taint",
"android.content;ComponentName;false;getPackageName;;;Argument[-1];ReturnValue;taint",
"android.content;ComponentName;false;getShortClassName;;;Argument[-1];ReturnValue;taint",
"android.content;ComponentName;false;unflattenFromString;;;Argument[0];ReturnValue;taint"
]
}
}

View File

@@ -0,0 +1,82 @@
/** Provides classes to reason about Android Intent redirect vulnerabilities. */
import java
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.frameworks.android.Intent
/**
* A sink for Intent redirection vulnerabilities in Android,
* that is, method calls that start Android components (like activities or services).
*/
abstract class IntentRedirectionSink extends DataFlow::Node { }
/** A sanitizer for data used to start an Android component. */
abstract class IntentRedirectionSanitizer extends DataFlow::Node { }
/**
* A unit class for adding additional taint steps.
*
* Extend this class to add additional taint steps that should apply to `IntentRedirectionConfiguration`.
*/
class IntentRedirectionAdditionalTaintStep extends Unit {
/**
* Holds if the step from `node1` to `node2` should be considered a taint
* step for the `IntentRedirectionConfiguration` configuration.
*/
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}
private class DefaultIntentRedirectionSinkModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
"android.app;Activity;true;bindService;;;Argument[0];intent-start",
"android.app;Activity;true;bindServiceAsUser;;;Argument[0];intent-start",
"android.app;Activity;true;startActivityAsCaller;;;Argument[0];intent-start",
"android.app;Activity;true;startActivityForResult;(Intent,int);;Argument[0];intent-start",
"android.app;Activity;true;startActivityForResult;(Intent,int,Bundle);;Argument[0];intent-start",
"android.app;Activity;true;startActivityForResult;(String,Intent,int,Bundle);;Argument[1];intent-start",
"android.app;Activity;true;startActivityForResultAsUser;;;Argument[0];intent-start",
"android.content;Context;true;startActivities;;;Argument[0];intent-start",
"android.content;Context;true;startActivity;;;Argument[0];intent-start",
"android.content;Context;true;startActivityAsUser;;;Argument[0];intent-start",
"android.content;Context;true;startActivityFromChild;;;Argument[1];intent-start",
"android.content;Context;true;startActivityFromFragment;;;Argument[1];intent-start",
"android.content;Context;true;startActivityIfNeeded;;;Argument[0];intent-start",
"android.content;Context;true;startForegroundService;;;Argument[0];intent-start",
"android.content;Context;true;startService;;;Argument[0];intent-start",
"android.content;Context;true;startServiceAsUser;;;Argument[0];intent-start",
"android.content;Context;true;sendBroadcast;;;Argument[0];intent-start",
"android.content;Context;true;sendBroadcastAsUser;;;Argument[0];intent-start",
"android.content;Context;true;sendBroadcastWithMultiplePermissions;;;Argument[0];intent-start",
"android.content;Context;true;sendStickyBroadcast;;;Argument[0];intent-start",
"android.content;Context;true;sendStickyBroadcastAsUser;;;Argument[0];intent-start",
"android.content;Context;true;sendStickyOrderedBroadcast;;;Argument[0];intent-start",
"android.content;Context;true;sendStickyOrderedBroadcastAsUser;;;Argument[0];intent-start"
]
}
}
/** Default sink for Intent redirection vulnerabilities. */
private class DefaultIntentRedirectionSink extends IntentRedirectionSink {
DefaultIntentRedirectionSink() { sinkNode(this, "intent-start") }
}
/**
* A default sanitizer for nodes dominated by calls to `ComponentName.getPackageName`
* or `ComponentName.getClassName`. These are used to check whether the origin or destination
* components are trusted.
*/
private class DefaultIntentRedirectionSanitizer extends IntentRedirectionSanitizer {
DefaultIntentRedirectionSanitizer() {
exists(MethodAccess ma, Method m, Guard g, boolean branch |
ma.getMethod() = m and
m.getDeclaringType() instanceof TypeComponentName and
m.hasName(["getPackageName", "getClassName"]) and
g.isEquality(ma, _, branch) and
g.controls(this.asExpr().getBasicBlock(), branch)
)
}
}

View File

@@ -0,0 +1,115 @@
/** Provides taint tracking configurations to be used in Android Intent redirection queries. */
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.DataFlow3
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.TaintTracking2
import semmle.code.java.security.AndroidIntentRedirection
/**
* A taint tracking configuration for tainted Intents being used to start Android components.
*/
class IntentRedirectionConfiguration extends TaintTracking::Configuration {
IntentRedirectionConfiguration() { this = "IntentRedirectionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof IntentRedirectionSink }
override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer instanceof IntentRedirectionSanitizer
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
any(IntentRedirectionAdditionalTaintStep c).step(node1, node2)
}
}
/**
* A sanitizer for sinks that receive the original incoming Intent,
* since its component cannot be arbitrarily set.
*/
private class OriginalIntentSanitizer extends IntentRedirectionSanitizer {
OriginalIntentSanitizer() { any(SameIntentBeingRelaunchedConfiguration c).hasFlowTo(this) }
}
/**
* Data flow configuration used to discard incoming Intents
* flowing directly to sinks that start Android components.
*/
private class SameIntentBeingRelaunchedConfiguration extends DataFlow3::Configuration {
SameIntentBeingRelaunchedConfiguration() { this = "SameIntentBeingRelaunchedConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof IntentRedirectionSink }
override predicate isBarrier(DataFlow::Node barrier) {
// Don't discard the Intent if its original component is tainted
barrier instanceof IntentWithTaintedComponent
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
// Intents being built with the copy constructor from the original Intent are discarded too
exists(ClassInstanceExpr cie |
cie.getConstructedType() instanceof TypeIntent and
node1.asExpr() = cie.getArgument(0) and
node1.asExpr().getType() instanceof TypeIntent and
node2.asExpr() = cie
)
}
}
/** An `Intent` with a tainted component. */
private class IntentWithTaintedComponent extends DataFlow::Node {
IntentWithTaintedComponent() {
exists(IntentSetComponent setExpr, TaintedIntentComponentConf conf |
setExpr.getQualifier() = this.asExpr() and
conf.hasFlowTo(DataFlow::exprNode(setExpr.getSink()))
)
}
}
/**
* A taint tracking configuration for tainted data flowing to an `Intent`'s component.
*/
private class TaintedIntentComponentConf extends TaintTracking2::Configuration {
TaintedIntentComponentConf() { this = "TaintedIntentComponentConf" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
any(IntentSetComponent setComponent).getSink() = sink.asExpr()
}
}
/** A call to a method that changes the component of an `Intent`. */
private class IntentSetComponent extends MethodAccess {
int sinkArg;
IntentSetComponent() {
exists(Method m |
this.getMethod() = m and
m.getDeclaringType() instanceof TypeIntent
|
m.hasName("setClass") and
sinkArg = 1
or
m.hasName("setClassName") and
exists(Parameter p |
p = m.getAParameter() and
p.getType() instanceof TypeString and
sinkArg = p.getPosition()
)
or
m.hasName("setComponent") and
sinkArg = 0
or
m.hasName("setPackage") and
sinkArg = 0
)
}
Expr getSink() { result = this.getArgument(sinkArg) }
}

View File

@@ -0,0 +1,34 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>An exported Android component that obtains a user-provided Intent and uses it to launch another component
can be exploited to obtain access to private, unexported components of the same app or to launch other apps' components
on behalf of the victim app.</p>
</overview>
<recommendation>
<p>Do not export components that start other components from a user-provided Intent.
They can be made private by setting the <code>android:exported</code> property to <code>false</code> in the app's Android Manifest.</p>
<p>If this is not possible, restrict either which apps can send Intents to the affected component, or which components can be started from it.</p>
</recommendation>
<example>
<p>The following snippet contains three examples.
In the first example, an arbitrary component can be started from the externally provided <code>forward_intent</code> Intent.
In the second example, the destination component of the Intent is first checked to make sure it is safe.
In the third example, the component that created the Intent is first checked to make sure it comes from a trusted origin.</p>
<sample src="AndroidIntentRedirectionSample.java" />
</example>
<references>
<li>
Google:
<a href="https://support.google.com/faqs/answer/9267555?hl=en">Remediation for Intent Redirection Vulnerability</a>.
</li>
<li>
OWASP Mobile Security Testing Guide:
<a href="https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05a-platform-overview#intents">Intents</a>.
</li>
<li>
Android Developers:
<a href="https://developer.android.com/guide/topics/manifest/activity-element#exported">The android:exported attribute</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,24 @@
/**
* @name Android Intent redirection
* @description Starting Android components with user-provided Intents
* can provide access to internal components of the application,
* increasing the attack surface and potentially causing unintended effects.
* @kind path-problem
* @problem.severity error
* @security-severity 7.5
* @precision high
* @id java/android/intent-redirection
* @tags security
* external/cwe/cwe-926
* external/cwe/cwe-940
*/
import java
import semmle.code.java.security.AndroidIntentRedirectionQuery
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, IntentRedirectionConfiguration conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Arbitrary Android activities or services can be started from $@.", source.getNode(),
"this user input"

View File

@@ -0,0 +1,18 @@
// BAD: A user-provided Intent is used to launch an arbitrary component
Intent forwardIntent = (Intent) getIntent().getParcelableExtra("forward_intent");
startActivity(forwardIntent);
// GOOD: The destination component is checked before launching it
Intent forwardIntent = (Intent) getIntent().getParcelableExtra("forward_intent");
ComponentName destinationComponent = forwardIntent.resolveActivity(getPackageManager());
if (destinationComponent.getPackageName().equals("safe.package") &&
destinationComponent.getClassName().equals("SafeClass")) {
startActivity(forwardIntent);
}
// GOOD: The component that sent the Intent is checked before launching the destination component
Intent forwardIntent = (Intent) getIntent().getParcelableExtra("forward_intent");
ComponentName originComponent = getCallingActivity();
if (originComponent.getPackageName().equals("trusted.package") && originComponent.getClassName().equals("TrustedClass")) {
startActivity(forwardIntent);
}

View File

@@ -29,7 +29,6 @@ public class IntentSources extends Activity {
}
class OtherClass {
private static void sink(Object o) {}

View File

@@ -1,5 +1,6 @@
package generatedtest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -1597,6 +1598,167 @@ public class Test {
out.readFromParcel(in);
sink(getMapValue(out)); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;ComponentName;(Context,Class);;Argument[1];Argument[-1];taint"
ComponentName out = null;
Class in = (Class) source();
out = new ComponentName((Context) null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;ComponentName;(Context,String);;Argument[1];Argument[-1];taint"
ComponentName out = null;
String in = (String) source();
out = new ComponentName((Context) null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;ComponentName;(Parcel);;Argument[0];Argument[-1];taint"
ComponentName out = null;
Parcel in = (Parcel) source();
out = new ComponentName(in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;ComponentName;(String,String);;Argument[0..1];Argument[-1];taint"
ComponentName out = null;
String in = (String) source();
out = new ComponentName(in, (String) null);
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;ComponentName;(String,String);;Argument[0..1];Argument[-1];taint"
ComponentName out = null;
String in = (String) source();
out = new ComponentName((String) null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;createRelative;(Context,String);;Argument[1];ReturnValue;taint"
ComponentName out = null;
String in = (String) source();
out = ComponentName.createRelative((Context) null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;createRelative;(String,String);;Argument[0..1];ReturnValue;taint"
ComponentName out = null;
String in = (String) source();
out = ComponentName.createRelative(in, (String) null);
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;createRelative;(String,String);;Argument[0..1];ReturnValue;taint"
ComponentName out = null;
String in = (String) source();
out = ComponentName.createRelative((String) null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;flattenToShortString;;;Argument[-1];ReturnValue;taint"
String out = null;
ComponentName in = (ComponentName) source();
out = in.flattenToShortString();
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;flattenToString;;;Argument[-1];ReturnValue;taint"
String out = null;
ComponentName in = (ComponentName) source();
out = in.flattenToString();
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;getClassName;;;Argument[-1];ReturnValue;taint"
String out = null;
ComponentName in = (ComponentName) source();
out = in.getClassName();
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;getPackageName;;;Argument[-1];ReturnValue;taint"
String out = null;
ComponentName in = (ComponentName) source();
out = in.getPackageName();
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;getShortClassName;;;Argument[-1];ReturnValue;taint"
String out = null;
ComponentName in = (ComponentName) source();
out = in.getShortClassName();
sink(out); // $ hasTaintFlow
}
{
// "android.content;ComponentName;false;unflattenFromString;;;Argument[0];ReturnValue;taint"
ComponentName out = null;
String in = (String) source();
out = ComponentName.unflattenFromString(in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;Intent;(Context,Class);;Argument[1];Argument[-1];taint"
Intent out = null;
Class in = (Class) source();
out = new Intent((Context) null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;Intent;(Intent);;Argument[0];Argument[-1];taint"
Intent out = null;
Intent in = (Intent) source();
out = new Intent(in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;Intent;(String,Uri,Context,Class);;Argument[3];Argument[-1];taint"
Intent out = null;
Class in = (Class) source();
out = new Intent(null, null, null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;setClass;;;Argument[1];Argument[-1];taint"
Intent out = null;
Class in = (Class) source();
out.setClass(null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;setClassName;(Context,String);;Argument[1];Argument[-1];taint"
Intent out = null;
String in = (String) source();
out.setClassName((Context) null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;setClassName;(String,String);;Argument[0..1];Argument[-1];taint"
Intent out = null;
String in = (String) source();
out.setClassName(in, (String) null);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;setClassName;(String,String);;Argument[0..1];Argument[-1];taint"
Intent out = null;
String in = (String) source();
out.setClassName((String) null, in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;setComponent;;;Argument[0];Argument[-1];taint"
Intent out = null;
ComponentName in = (ComponentName) source();
out.setComponent(in);
sink(out); // $ hasTaintFlow
}
{
// "android.content;Intent;true;setPackage;;;Argument[0];Argument[-1];taint"
Intent out = null;
String in = (String) source();
out.setPackage(in);
sink(out); // $ hasTaintFlow
}
}

View File

@@ -0,0 +1,197 @@
package com.example.app;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class AndroidIntentRedirectionTest extends Activity {
public void onCreate(Bundle savedInstanceState) {
Intent intent = (Intent) getIntent().getParcelableExtra("forward_intent");
// @formatter:off
startActivities(new Intent[] {intent}); // $ hasAndroidIntentRedirection
startActivities(new Intent[] {intent}, null); // $ hasAndroidIntentRedirection
startActivity(intent); // $ hasAndroidIntentRedirection
startActivity(intent, null); // $ hasAndroidIntentRedirection
startActivityAsUser(intent, null); // $ hasAndroidIntentRedirection
startActivityAsCaller(intent, null, false, 0); // $ hasAndroidIntentRedirection
startActivityForResult(intent, 0); // $ hasAndroidIntentRedirection
startActivityForResult(intent, 0, null); // $ hasAndroidIntentRedirection
startActivityForResult(null, intent, 0, null); // $ hasAndroidIntentRedirection
startActivityForResultAsUser(intent, null, 0, null, null); // $ hasAndroidIntentRedirection
startActivityForResultAsUser(intent, 0, null, null); // $ hasAndroidIntentRedirection
startActivityForResultAsUser(intent, 0, null); // $ hasAndroidIntentRedirection
bindService(intent, null, 0);
bindServiceAsUser(intent, null, 0, null);
startService(intent); // $ hasAndroidIntentRedirection
startServiceAsUser(intent, null); // $ hasAndroidIntentRedirection
startForegroundService(intent); // $ hasAndroidIntentRedirection
sendBroadcast(intent); // $ hasAndroidIntentRedirection
sendBroadcast(intent, null); // $ hasAndroidIntentRedirection
sendBroadcastAsUser(intent, null); // $ hasAndroidIntentRedirection
sendBroadcastAsUser(intent, null, null); // $ hasAndroidIntentRedirection
sendBroadcastWithMultiplePermissions(intent, null); // $ hasAndroidIntentRedirection
sendStickyBroadcast(intent); // $ hasAndroidIntentRedirection
sendStickyBroadcastAsUser(intent, null); // $ hasAndroidIntentRedirection
sendStickyOrderedBroadcast(intent, null, null, 0, null, null); // $ hasAndroidIntentRedirection
sendStickyOrderedBroadcastAsUser(intent, null, null, null, 0, null, null); // $ hasAndroidIntentRedirection
// @formatter:on
if (intent.getComponent().getPackageName().equals("something")) {
startActivity(intent); // Safe - sanitized
} else {
startActivity(intent); // $ hasAndroidIntentRedirection
}
if (intent.getComponent().getClassName().equals("something")) {
startActivity(intent); // Safe - sanitized
} else {
startActivity(intent); // $ hasAndroidIntentRedirection
}
try {
{
// Delayed cast
Object obj = getIntent().getParcelableExtra("forward_intent");
Intent fwdIntent = (Intent) obj;
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
fwdIntent.setClassName((Context) null, intent.getStringExtra("className"));
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
fwdIntent.setClassName(intent.getStringExtra("packageName"), null);
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
fwdIntent.setClassName(intent.getStringExtra("packageName"),
intent.getStringExtra("className"));
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
fwdIntent.setClass(null, Class.forName(intent.getStringExtra("className")));
// needs taint step for Class.forName
startActivity(fwdIntent); // $ MISSING: $hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
fwdIntent.setPackage(intent.getStringExtra("packageName"));
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
ComponentName component =
new ComponentName(intent.getStringExtra("packageName"), null);
fwdIntent.setComponent(component);
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
ComponentName component =
new ComponentName("", intent.getStringExtra("className"));
fwdIntent.setComponent(component);
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
ComponentName component =
new ComponentName((Context) null, intent.getStringExtra("className"));
fwdIntent.setComponent(component);
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
ComponentName component = new ComponentName((Context) null,
Class.forName(intent.getStringExtra("className")));
fwdIntent.setComponent(component);
// needs taint step for Class.forName
startActivity(fwdIntent); // $ MISSING: $hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
ComponentName component =
ComponentName.createRelative("", intent.getStringExtra("className"));
fwdIntent.setComponent(component);
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
ComponentName component =
ComponentName.createRelative(intent.getStringExtra("packageName"), "");
fwdIntent.setComponent(component);
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = new Intent();
ComponentName component = ComponentName.createRelative((Context) null,
intent.getStringExtra("className"));
fwdIntent.setComponent(component);
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent originalIntent = getIntent();
ComponentName cp = new ComponentName(originalIntent.getStringExtra("packageName"),
originalIntent.getStringExtra("className"));
Intent anotherIntent = new Intent();
anotherIntent.setComponent(cp);
startActivity(originalIntent); // Safe - not a tainted Intent
}
{
Intent originalIntent = getIntent();
Intent anotherIntent = new Intent(originalIntent);
startActivity(anotherIntent); // Safe - copy constructor from original Intent
}
{
Intent originalIntent = getIntent();
Intent fwdIntent = (Intent) originalIntent.getParcelableExtra("forward_intent");
if (originalIntent.getBooleanExtra("use_fwd_intent", false)) {
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
} else {
startActivity(originalIntent); // Safe - not an Intent obtained from the Extras
}
}
{
Intent originalIntent = getIntent();
originalIntent.setClassName(originalIntent.getStringExtra("package_name"),
originalIntent.getStringExtra("class_name"));
startActivity(originalIntent); // $ hasAndroidIntentRedirection
}
{
Intent originalIntent = getIntent();
originalIntent.setClassName("not_user_provided", "not_user_provided");
startActivity(originalIntent); // Safe - component changed but not tainted
}
{
Intent originalIntent = getIntent();
Intent fwdIntent;
if (originalIntent.getBooleanExtra("use_fwd_intent", false)) {
fwdIntent = (Intent) originalIntent.getParcelableExtra("forward_intent");
} else {
fwdIntent = originalIntent;
}
// Conditionally tainted sinks aren't supported currently
startActivity(fwdIntent); // $ MISSING: $hasAndroidIntentRedirection
}
{
Intent fwdIntent = Intent.parseUri(getIntent().getStringExtra("uri"), 0);
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = Intent.getIntent(getIntent().getStringExtra("uri"));
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
{
Intent fwdIntent = Intent.getIntentOld(getIntent().getStringExtra("uri"));
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
}
} catch (Exception e) {
}
}
}

View File

@@ -0,0 +1,20 @@
import java
import semmle.code.java.security.AndroidIntentRedirectionQuery
import TestUtilities.InlineExpectationsTest
class HasAndroidIntentRedirectionTest extends InlineExpectationsTest {
HasAndroidIntentRedirectionTest() { this = "HasAndroidIntentRedirectionTest" }
override string getARelevantTag() { result = "hasAndroidIntentRedirection" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasAndroidIntentRedirection" and
exists(DataFlow::Node src, DataFlow::Node sink, IntentRedirectionConfiguration conf |
conf.hasFlow(src, sink)
|
sink.getLocation() = location and
element = sink.toString() and
value = ""
)
}
}

View File

@@ -0,0 +1,24 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app"
android:installLocation="auto"
android:versionCode="1"
android:versionName="0.1" >
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".AndroidIntentRedirectionTest"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SafeActivity" />
</application>
</manifest>

View File

@@ -0,0 +1 @@
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/google-android-9.0.0

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.annotation;
public @interface NonNull {
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package android.annotation;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
public @interface RequiresPermission {
String value() default "";
String[] allOf() default {};
String[] anyOf() default {};
boolean conditional() default false;
@Target({FIELD, METHOD, PARAMETER})
@interface Read {
RequiresPermission value() default @RequiresPermission;
}
@Target({FIELD, METHOD, PARAMETER})
@interface Write {
RequiresPermission value() default @RequiresPermission;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package android.app;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
public class Fragment {
public static class SavedState implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {}
@Override
public int describeContents() {
return 0;
}
}
static public class InstantiationException {
public InstantiationException(String msg, Exception cause) {}
}
public Fragment() {}
public static Fragment instantiate(Context context, String fname) {
return null;
}
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
return null;
}
@Override
final public boolean equals(Object o) {
return false;
}
@Override
final public int hashCode() {
return 0;
}
@Override
public String toString() {
return null;
}
}

View File

@@ -2,7 +2,6 @@
package android.content;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;

View File

@@ -2,15 +2,11 @@
package android.content;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.concurrent.Executor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -29,11 +25,6 @@ import android.os.Looper;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.Display;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.concurrent.Executor;
abstract public class Context
{
@@ -45,6 +36,7 @@ abstract public class Context
public abstract ClassLoader getClassLoader();
public abstract ComponentName startForegroundService(Intent p0);
public abstract ComponentName startService(Intent p0);
public abstract ComponentName startServiceAsUser(Intent p0, UserHandle p1);
public abstract ContentResolver getContentResolver();
public abstract Context createConfigurationContext(Configuration p0);
public abstract Context createContextForSplit(String p0);
@@ -90,6 +82,7 @@ abstract public class Context
public abstract String[] databaseList();
public abstract String[] fileList();
public abstract boolean bindService(Intent p0, ServiceConnection p1, int p2);
public abstract boolean bindServiceAsUser(Intent p0, ServiceConnection p1, int p2, UserHandle p3);
public abstract boolean deleteDatabase(String p0);
public abstract boolean deleteFile(String p0);
public abstract boolean deleteSharedPreferences(String p0);
@@ -141,6 +134,7 @@ abstract public class Context
public abstract void startActivities(Intent[] p0, Bundle p1);
public abstract void startActivity(Intent p0);
public abstract void startActivity(Intent p0, Bundle p1);
public abstract void startActivityAsUser(Intent intent, UserHandle user);
public abstract void startIntentSender(IntentSender p0, Intent p1, int p2, int p3, int p4);
public abstract void startIntentSender(IntentSender p0, Intent p1, int p2, int p3, int p4, Bundle p5);
public abstract void unbindService(ServiceConnection p0);

View File

@@ -15,22 +15,16 @@
*/
package android.content;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.concurrent.Executor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
@@ -40,13 +34,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.Display;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.concurrent.Executor;
/**
* Proxying implementation of Context that simply delegates all of its calls to
@@ -54,7 +42,15 @@ import java.util.concurrent.Executor;
* the original Context.
*/
public class ContextWrapper extends Context {
public ContextWrapper() {
public ContextWrapper() {}
public Context getBaseContext() {
return null;
}
@Override
public Executor getMainExecutor() {
return null;
}
public ContextWrapper(Context base) {
@@ -75,6 +71,7 @@ public class ContextWrapper extends Context {
@Override public ClassLoader getClassLoader() { return null; }
@Override public ComponentName startForegroundService(Intent p0) { return null; }
@Override public ComponentName startService(Intent p0) { return null; }
@Override public ComponentName startServiceAsUser(Intent p0, UserHandle p1) { return null; }
@Override public ContentResolver getContentResolver() { return null; }
@Override public Context createConfigurationContext(Configuration p0) { return null; }
@Override public Context createContextForSplit(String p0) { return null; }
@@ -120,6 +117,7 @@ public class ContextWrapper extends Context {
@Override public String[] databaseList() { return null; }
@Override public String[] fileList() { return null; }
@Override public boolean bindService(Intent p0, ServiceConnection p1, int p2) { return false; }
@Override public boolean bindServiceAsUser(Intent p0, ServiceConnection p1, int p2, UserHandle p3) { return false; }
@Override public boolean deleteDatabase(String p0) { return false; }
@Override public boolean deleteFile(String p0) { return false; }
@Override public boolean deleteSharedPreferences(String p0) { return false; }
@@ -171,6 +169,7 @@ public class ContextWrapper extends Context {
@Override public void startActivities(Intent[] p0, Bundle p1) { }
@Override public void startActivity(Intent p0) { }
@Override public void startActivity(Intent p0, Bundle p1) { }
@Override public void startActivityAsUser(Intent p0, UserHandle p1) { }
@Override public void startIntentSender(IntentSender p0, Intent p1, int p2, int p3, int p4) { }
@Override public void startIntentSender(IntentSender p0, Intent p1, int p2, int p3, int p4, Bundle p5) { }
@Override public void unbindService(ServiceConnection p0) { }

View File

@@ -2,11 +2,10 @@
package android.content;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IntentSender;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Set;
import org.xmlpull.v1.XmlPullParser;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -16,10 +15,6 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Set;
import org.xmlpull.v1.XmlPullParser;
public class Intent implements Cloneable, Parcelable
{

View File

@@ -2,18 +2,16 @@
package android.content;
import android.content.ContentResolver;
import android.content.Intent;
import java.util.Iterator;
import java.util.Set;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
import android.util.AndroidException;
import android.util.Printer;
import java.util.Iterator;
import java.util.Set;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
public class IntentFilter implements Parcelable
{

View File

@@ -2,7 +2,6 @@
package android.content;
import android.content.ComponentName;
import android.os.IBinder;
public interface ServiceConnection

View File

@@ -2,8 +2,6 @@
package android.os;
import android.os.Looper;
import android.os.Message;
import android.util.Printer;
public class Handler

View File

@@ -2,9 +2,6 @@
package android.os;
import android.os.Parcel;
import android.os.Parcelable;
public class UserHandle implements Parcelable
{
protected UserHandle() {}

View File

@@ -1,602 +1,150 @@
/*
* Copyright (C) 2006 The Android Open Source Project
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package android.webkit;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import java.io.File;
import java.util.List;
import java.util.Map;
/**
* <p>
* A View that displays web pages. This class is the basis upon which you can
* roll your own web browser or simply display some online content within your
* Activity. It uses the WebKit rendering engine to display web pages and
* includes methods to navigate forward and backward through a history, zoom in
* and out, perform text searches and more.
*
* <p>
* Note that, in order for your Activity to access the Internet and load web
* pages in a WebView, you must add the {@code INTERNET} permissions to your
* Android Manifest file:
*
* <pre>
* {@code <uses-permission android:name="android.permission.INTERNET" />}
* </pre>
*
* <p>
* This must be a child of the <a href="
* {@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
* element.
*
* <p>
* For more information, read
* <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in
* WebView</a>.
*
* <h3>Basic usage</h3>
*
* <p>
* By default, a WebView provides no browser-like widgets, does not enable
* JavaScript and web page errors are ignored. If your goal is only to display
* some HTML as a part of your UI, this is probably fine; the user won't need to
* interact with the web page beyond reading it, and the web page won't need to
* interact with the user. If you actually want a full-blown web browser, then
* you probably want to invoke the Browser application with a URL Intent rather
* than show it with a WebView. For example:
*
* <pre>
* Uri uri = Uri.parse("https://www.example.com");
* Intent intent = new Intent(Intent.ACTION_VIEW, uri);
* startActivity(intent);
* </pre>
* <p>
* See {@link android.content.Intent} for more information.
*
* <p>
* To provide a WebView in your own Activity, include a {@code <WebView>} in
* your layout, or set the entire Activity window as a WebView during
* {@link android.app.Activity#onCreate(Bundle) onCreate()}:
*
* <pre class="prettyprint">
* WebView webview = new WebView(this);
* setContentView(webview);
* </pre>
*
* <p>
* Then load the desired web page:
*
* <pre>
* // Simplest usage: note that an exception will NOT be thrown
* // if there is an error loading this page (see below).
* webview.loadUrl("https://example.com/");
*
* // OR, you can also load from an HTML string:
* String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
* webview.loadData(summary, "text/html", null);
* // ... although note that there are restrictions on what this HTML can do.
* // See {@link #loadData(String,String,String)} and {@link
* #loadDataWithBaseURL(String,String,String,String,String)} for more info.
* // Also see {@link #loadData(String,String,String)} for information on encoding special
* // characters.
* </pre>
*
* <p>
* A WebView has several customization points where you can add your own
* behavior. These are:
*
* <ul>
* <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
* This class is called when something that might impact a browser UI happens,
* for instance, progress updates and JavaScript alerts are sent here (see
* <a href="
* {@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging
* Tasks</a>).</li>
* <li>Creating and setting a {@link android.webkit.WebViewClient} subclass. It
* will be called when things happen that impact the rendering of the content,
* eg, errors or form submissions. You can also intercept URL loading here (via
* {@link android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
* shouldOverrideUrlLoading()}).</li>
* <li>Modifying the {@link android.webkit.WebSettings}, such as enabling
* JavaScript with
* {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
* setJavaScriptEnabled()}.</li>
* <li>Injecting Java objects into the WebView using the
* {@link android.webkit.WebView#addJavascriptInterface} method. This method
* allows you to inject Java objects into a page's JavaScript context, so that
* they can be accessed by JavaScript in the page.</li>
* </ul>
*
* <p>
* Here's a more complicated example, showing error handling, settings, and
* progress notification:
*
* <pre class="prettyprint">
* // Let's display the progress in the activity title bar, like the
* // browser app does.
* getWindow().requestFeature(Window.FEATURE_PROGRESS);
*
* webview.getSettings().setJavaScriptEnabled(true);
*
* final Activity activity = this;
* webview.setWebChromeClient(new WebChromeClient() {
* public void onProgressChanged(WebView view, int progress) {
* // Activities and WebViews measure progress with different scales.
* // The progress meter will automatically disappear when we reach 100%
* activity.setProgress(progress * 1000);
* }
* });
* webview.setWebViewClient(new WebViewClient() {
* public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
* Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
* }
* });
*
* webview.loadUrl("https://developer.android.com/");
* </pre>
*
* <h3>Zoom</h3>
*
* <p>
* To enable the built-in zoom, set {@link #getSettings()
* WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} (introduced
* in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
*
* <p class="note">
* <b>Note:</b> Using zoom if either the height or width is set to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to
* undefined behavior and should be avoided.
*
* <h3>Cookie and window management</h3>
*
* <p>
* For obvious security reasons, your application has its own cache, cookie
* store etc.&mdash;it does not share the Browser application's data.
*
* <p>
* By default, requests by the HTML to open new windows are ignored. This is
* {@code true} whether they be opened by JavaScript or by the target attribute
* on a link. You can customize your {@link WebChromeClient} to provide your own
* behavior for opening multiple windows, and render them in whatever manner you
* want.
*
* <p>
* The standard behavior for an Activity is to be destroyed and recreated when
* the device orientation or any other configuration changes. This will cause
* the WebView to reload the current page. If you don't want that, you can set
* your Activity to handle the {@code orientation} and {@code keyboardHidden}
* changes, and then just leave the WebView alone. It'll automatically re-orient
* itself as appropriate. Read
* <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling
* Runtime Changes</a> for more information about how to handle configuration
* changes during runtime.
*
*
* <h3>Building web pages to support different screen densities</h3>
*
* <p>
* The screen density of a device is based on the screen resolution. A screen
* with low density has fewer available pixels per inch, where a screen with
* high density has more &mdash; sometimes significantly more &mdash; pixels per
* inch. The density of a screen is important because, other things being equal,
* a UI element (such as a button) whose height and width are defined in terms
* of screen pixels will appear larger on the lower density screen and smaller
* on the higher density screen. For simplicity, Android collapses all actual
* screen densities into three generalized densities: high, medium, and low.
* <p>
* By default, WebView scales a web page so that it is drawn at a size that
* matches the default appearance on a medium density screen. So, it applies
* 1.5x scaling on a high density screen (because its pixels are smaller) and
* 0.75x scaling on a low density screen (because its pixels are bigger).
* Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR},
* WebView supports DOM, CSS, and meta tag features to help you (as a web
* developer) target screens with different screen densities.
* <p>
* Here's a summary of the features you can use to handle different screen
* densities:
* <ul>
* <li>The {@code window.devicePixelRatio} DOM property. The value of this
* property specifies the default scaling factor used for the current device.
* For example, if the value of {@code
* window.devicePixelRatio} is "1.0", then the device is considered a medium
* density (mdpi) device and default scaling is not applied to the web page; if
* the value is "1.5", then the device is considered a high density device
* (hdpi) and the page content is scaled 1.5x; if the value is "0.75", then the
* device is considered a low density device (ldpi) and the content is scaled
* 0.75x.</li>
* <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to
* specify the screen densities for which this style sheet is to be used. The
* corresponding value should be either "0.75", "1", or "1.5", to indicate that
* the styles are for devices with low density, medium density, or high density
* screens, respectively. For example:
*
* <pre>
* &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;
* </pre>
* <p>
* The {@code hdpi.css} stylesheet is only used for devices with a screen pixel
* ratio of 1.5, which is the high density pixel ratio.</li>
* </ul>
*
* <h3>HTML5 Video support</h3>
*
* <p>
* In order to support inline HTML5 video in your application you need to have
* hardware acceleration turned on.
*
* <h3>Full screen support</h3>
*
* <p>
* In order to support full screen &mdash; for video or other HTML content
* &mdash; you need to set a {@link android.webkit.WebChromeClient} and
* implement both
* {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
* and {@link WebChromeClient#onHideCustomView()}. If the implementation of
* either of these two methods is missing then the web contents will not be
* allowed to enter full screen. Optionally you can implement
* {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View
* displayed whilst a video is loading.
*
* <h3>HTML5 Geolocation API support</h3>
*
* <p>
* For applications targeting Android N and later releases (API level >
* {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only
* supported on secure origins such as https. For such applications requests to
* geolocation api on non-secure origins are automatically denied without
* invoking the corresponding
* {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)}
* method.
*
* <h3>Layout size</h3>
* <p>
* It is recommended to set the WebView layout height to a fixed value or to
* {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}. When using
* {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} for the height none
* of the WebView's parents should use a
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since
* that could result in incorrect sizing of the views.
*
* <p>
* Setting the WebView's height to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} enables the
* following behaviors:
* <ul>
* <li>The HTML body layout height is set to a fixed value. This means that
* elements with a height relative to the HTML body may not be sized correctly.
* </li>
* <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT}
* and earlier SDKs the HTML viewport meta tag will be ignored in order to
* preserve backwards compatibility.</li>
* </ul>
*
* <p>
* Using a layout width of
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not supported. If
* such a width is used the WebView will attempt to use the width of the parent
* instead.
*
* <h3>Metrics</h3>
*
* <p>
* WebView may upload anonymous diagnostic data to Google when the user has
* consented. This data helps Google improve WebView. Data is collected on a
* per-app basis for each app which has instantiated a WebView. An individual
* app can opt out of this feature by putting the following tag in its
* manifest's {@code <application>} element:
*
* <pre>
* &lt;manifest&gt;
* &lt;application&gt;
* ...
* &lt;meta-data android:name=&quot;android.webkit.WebView.MetricsOptOut&quot;
* android:value=&quot;true&quot; /&gt;
* &lt;/application&gt;
* &lt;/manifest&gt;
* </pre>
* <p>
* Data will only be uploaded for a given app if the user has consented AND the
* app has not opted out.
*
* <h3>Safe Browsing</h3>
*
* <p>
* With Safe Browsing, WebView will block malicious URLs and present a warning
* UI to the user to allow them to navigate back safely or proceed to the
* malicious page.
* <p>
* Safe Browsing is enabled by default on devices which support it. If your app
* needs to disable Safe Browsing for all WebViews, it can do so in the
* manifest's {@code <application>} element:
* <p>
*
* <pre>
* &lt;manifest&gt;
* &lt;application&gt;
* ...
* &lt;meta-data android:name=&quot;android.webkit.WebView.EnableSafeBrowsing&quot;
* android:value=&quot;false&quot; /&gt;
* &lt;/application&gt;
* &lt;/manifest&gt;
* </pre>
*
* <p>
* Otherwise, see {@link WebSettings#setSafeBrowsingEnabled}.
*
*/
// Implementation notes.
// The WebView is a thin API class that delegates its public API to a backend
// WebViewProvider
// class instance. WebView extends {@link AbsoluteLayout} for backward
// compatibility reasons.
// Methods are delegated to the provider implementation: all public API methods
// introduced in this
// file are fully delegated, whereas public and protected methods from the View
// base classes are
// only delegated where a specific need exists for them to do so.
public class WebView {
/**
* Constructs a new WebView with an Activity Context object.
*
* <p class="note">
* <b>Note:</b> WebView should always be instantiated with an Activity Context.
* If instantiated with an Application Context, WebView will be unable to
* provide several features, such as JavaScript dialogs and autofill.
*
* @param context an Activity Context to access application assets
*/
public class WebView extends View {
public WebView(Context context) {
super(context);
}
/**
* Loads the given URL with the specified additional HTTP headers.
* <p>
* Also see compatibility note on {@link #evaluateJavascript}.
*
* @param url the URL of the resource to load
* @param additionalHttpHeaders the additional headers to be used in the HTTP
* request for this URL, specified as a map from
* name to value. Note that if this map contains
* any of the headers that are set by default by
* this WebView, such as those controlling caching,
* accept types or the User-Agent, their values may
* be overridden by this WebView's defaults.
*/
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
public void setHorizontalScrollbarOverlay(boolean overlay) {}
public void setVerticalScrollbarOverlay(boolean overlay) {}
public boolean overlayHorizontalScrollbar() {
return false;
}
/**
* Loads the given URL.
* <p>
* Also see compatibility note on {@link #evaluateJavascript}.
*
* @param url the URL of the resource to load
*/
public void loadUrl(String url) {
public boolean overlayVerticalScrollbar() {
return false;
}
/**
* Loads the URL with postData using "POST" method into this WebView. If url is
* not a network URL, it will be loaded with {@link #loadUrl(String)} instead,
* ignoring the postData param.
*
* @param url the URL of the resource to load
* @param postData the data will be passed to "POST" request, which must be be
* "application/x-www-form-urlencoded" encoded.
*/
public void postUrl(String url, byte[] postData) {
}
public void savePassword(String host, String username, String password) {}
/**
* Loads the given data into this WebView using a 'data' scheme URL.
* <p>
* Note that JavaScript's same origin policy means that script running in a page
* loaded using this method will be unable to access content loaded using any
* scheme other than 'data', including 'http(s)'. To avoid this restriction, use
* {@link #loadDataWithBaseURL(String,String,String,String,String)
* loadDataWithBaseURL()} with an appropriate base URL.
* <p>
* The {@code encoding} parameter specifies whether the data is base64 or URL
* encoded. If the data is base64 encoded, the value of the encoding parameter
* must be 'base64'. HTML can be encoded with
* {@link android.util.Base64#encodeToString(byte[],int)} like so:
*
* <pre>
* String unencodedHtml = "&lt;html&gt;&lt;body&gt;'%28' is the code for '('&lt;/body&gt;&lt;/html&gt;";
* String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING);
* webView.loadData(encodedHtml, "text/html", "base64");
* </pre>
* <p>
* For all other values of {@code encoding} (including {@code null}) it is
* assumed that the data uses ASCII encoding for octets inside the range of safe
* URL characters and use the standard %xx hex encoding of URLs for octets
* outside that range. See
* <a href="https://tools.ietf.org/html/rfc3986#section-2.2">RFC 3986</a> for
* more information.
* <p>
* The {@code mimeType} parameter specifies the format of the data. If WebView
* can't handle the specified MIME type, it will download the data. If
* {@code null}, defaults to 'text/html'.
* <p>
* The 'data' scheme URL formed by this method uses the default US-ASCII
* charset. If you need to set a different charset, you should form a 'data'
* scheme URL which explicitly specifies a charset parameter in the mediatype
* portion of the URL and call {@link #loadUrl(String)} instead. Note that the
* charset obtained from the mediatype portion of a data URL always overrides
* that specified in the HTML or XML document itself.
* <p>
* Content loaded using this method will have a {@code window.origin} value of
* {@code "null"}. This must not be considered to be a trusted origin by the
* application or by any JavaScript code running inside the WebView (for
* example, event sources in DOM event handlers or web messages), because
* malicious content can also create frames with a null origin. If you need to
* identify the main frame's origin in a trustworthy way, you should use
* {@link #loadDataWithBaseURL(String,String,String,String,String)
* loadDataWithBaseURL()} with a valid HTTP or HTTPS base URL to set the origin.
*
* @param data a String of data in the given encoding
* @param mimeType the MIME type of the data, e.g. 'text/html'.
* @param encoding the encoding of the data
*/
public void loadData(String data, String mimeType, String encoding) {
}
public void setHttpAuthUsernamePassword(String host, String realm, String username,
String password) {}
/**
* Loads the given data into this WebView, using baseUrl as the base URL for the
* content. The base URL is used both to resolve relative URLs and when applying
* JavaScript's same origin policy. The historyUrl is used for the history
* entry.
* <p>
* The {@code mimeType} parameter specifies the format of the data. If WebView
* can't handle the specified MIME type, it will download the data. If
* {@code null}, defaults to 'text/html'.
* <p>
* Note that content specified in this way can access local device files (via
* 'file' scheme URLs) only if baseUrl specifies a scheme other than 'http',
* 'https', 'ftp', 'ftps', 'about' or 'javascript'.
* <p>
* If the base URL uses the data scheme, this method is equivalent to calling
* {@link #loadData(String,String,String) loadData()} and the historyUrl is
* ignored, and the data will be treated as part of a data: URL. If the base URL
* uses any other scheme, then the data will be loaded into the WebView as a
* plain string (i.e. not part of a data URL) and any URL-encoded entities in
* the string will not be decoded.
* <p>
* Note that the baseUrl is sent in the 'Referer' HTTP header when requesting
* subresources (images, etc.) of the page loaded using this method.
* <p>
* If a valid HTTP or HTTPS base URL is not specified in {@code baseUrl}, then
* content loaded using this method will have a {@code window.origin} value of
* {@code "null"}. This must not be considered to be a trusted origin by the
* application or by any JavaScript code running inside the WebView (for
* example, event sources in DOM event handlers or web messages), because
* malicious content can also create frames with a null origin. If you need to
* identify the main frame's origin in a trustworthy way, you should use a valid
* HTTP or HTTPS base URL to set the origin.
*
* @param baseUrl the URL to use as the page's base URL. If {@code null}
* defaults to 'about:blank'.
* @param data a String of data in the given encoding
* @param mimeType the MIME type of the data, e.g. 'text/html'.
* @param encoding the encoding of the data
* @param historyUrl the URL to use as the history entry. If {@code null}
* defaults to 'about:blank'. If non-null, this must be a
* valid URL.
*/
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) {
}
/**
* Sets the WebViewClient that will receive various notifications and
* requests. This will replace the current handler.
*
* @param client an implementation of WebViewClient
* @see #getWebViewClient
*/
public void setWebViewClient(WebViewClient client) {
}
/**
* Gets the WebViewClient.
*
* @return the WebViewClient, or a default client if not yet set
* @see #setWebViewClient
*/
public WebViewClient getWebViewClient() {
public String[] getHttpAuthUsernamePassword(String host, String realm) {
return null;
}
/**
* Injects the supplied Java object into this WebView. The object is
* injected into the JavaScript context of the main frame, using the
* supplied name. This allows the Java object's methods to be
* accessed from JavaScript. For applications targeted to API
* level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
* and above, only public methods that are annotated with
* {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
* For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
* all public methods (including the inherited ones) can be accessed, see the
* important security note below for implications.
* <p> Note that injected objects will not appear in JavaScript until the page is next
* (re)loaded. JavaScript should be enabled before injecting the object. For example:
* <pre>
* class JsObject {
* {@literal @}JavascriptInterface
* public String toString() { return "injectedObject"; }
* }
* webview.getSettings().setJavaScriptEnabled(true);
* webView.addJavascriptInterface(new JsObject(), "injectedObject");
* webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
* webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
* <p>
* <strong>IMPORTANT:</strong>
* <ul>
* <li> This method can be used to allow JavaScript to control the host
* application. This is a powerful feature, but also presents a security
* risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier.
* Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
* are still vulnerable if the app runs on a device running Android earlier than 4.2.
* The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
* and to ensure the method is called only when running on Android 4.2 or later.
* With these older versions, JavaScript could use reflection to access an
* injected object's public fields. Use of this method in a WebView
* containing untrusted content could allow an attacker to manipulate the
* host application in unintended ways, executing Java code with the
* permissions of the host application. Use extreme care when using this
* method in a WebView which could contain untrusted content.</li>
* <li> JavaScript interacts with Java object on a private, background
* thread of this WebView. Care is therefore required to maintain thread
* safety.
* </li>
* <li> The Java object's fields are not accessible.</li>
* <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
* and above, methods of injected Java objects are enumerable from
* JavaScript.</li>
* </ul>
*
* @param object the Java object to inject into this WebView's JavaScript
* context. {@code null} values are ignored.
* @param name the name used to expose the object in JavaScript
*/
public void addJavascriptInterface(Object object, String name) {
}
/**
* Removes a previously injected Java object from this WebView. Note that
* the removal will not be reflected in JavaScript until the page is next
* (re)loaded. See {@link #addJavascriptInterface}.
*
* @param name the name used to expose the object in JavaScript
*/
public void removeJavascriptInterface(String name) {
public void destroy() {}
public static void enablePlatformNotifications() {}
public static void disablePlatformNotifications() {}
public void loadUrl(String url) {}
public void loadData(String data, String mimeType, String encoding) {}
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding,
String failUrl) {}
public void stopLoading() {}
public void reload() {}
public boolean canGoBack() {
return false;
}
public void goBack() {}
public boolean canGoForward() {
return false;
}
public void goForward() {}
public boolean canGoBackOrForward(int steps) {
return false;
}
public void goBackOrForward(int steps) {}
public boolean pageUp(boolean top) {
return false;
}
public boolean pageDown(boolean bottom) {
return false;
}
public void clearView() {}
public float getScale() {
return 0;
}
public void setInitialScale(int scaleInPercent) {}
public void invokeZoomPicker() {}
public String getUrl() {
return null;
}
public String getTitle() {
return null;
}
public int getProgress() {
return 0;
}
public int getContentHeight() {
return 0;
}
public void pauseTimers() {}
public void resumeTimers() {}
public void clearCache() {}
public void clearFormData() {}
public void clearHistory() {}
public void clearSslPreferences() {}
public static String findAddress(String addr) {
return null;
}
public void setWebViewClient(WebViewClient client) {}
public void addJavascriptInterface(Object obj, String interfaceName) {}
public View getZoomControls() {
return null;
}
public boolean zoomIn() {
return false;
}
public boolean zoomOut() {
return false;
}
/**
* Gets the WebSettings object used to control the settings for this
* WebView.
*
* @return a WebSettings object that can be used to control this WebView's
* settings
*/
public WebSettings getSettings() {
return null;
}
}