455 lines
16 KiB
Java
455 lines
16 KiB
Java
/*
|
|
* This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html.
|
|
*
|
|
* This file is a derivative of code released under the terms listed below.
|
|
*
|
|
*/
|
|
/*
|
|
* Copyright (c) 2013,
|
|
* Tobias Blaschke <code@tobiasblaschke.de>
|
|
* All rights reserved.
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* 3. The names of the contributors may not be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
package com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa;
|
|
|
|
import com.ibm.wala.dalvik.util.AndroidComponent;
|
|
import com.ibm.wala.dalvik.util.AndroidEntryPointManager;
|
|
import com.ibm.wala.ipa.callgraph.ContextItem;
|
|
import com.ibm.wala.ipa.callgraph.ContextKey;
|
|
import com.ibm.wala.types.TypeName;
|
|
import com.ibm.wala.util.strings.Atom;
|
|
|
|
/**
|
|
* Determines the target of an Android-Intent.
|
|
*
|
|
* An Intent is generated at each Constructor-Call of an Android-Intent. Information to that Constructor
|
|
* is added as target.
|
|
*
|
|
* If you want to change the target don't change this Object! Instead place an override using the
|
|
* AndroidEntryPointManager so no information is lost.
|
|
*
|
|
* Upon the creation of an Intent it's target-type almost always points to UNKNOWN. Again change this
|
|
* using an override.
|
|
*
|
|
* This class contains functions that determine the target-type on the Intent itself. They are intended
|
|
* as fallback only.
|
|
*
|
|
* CAUTION: If you inherit from this class - keep hashCodes and equals clashing!
|
|
*
|
|
* @see com.ibm.wala.dalvik.util.AndroidEntryPointManager
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.IntentContextSelector
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.IntentContextInterpreter
|
|
*
|
|
* @author Tobias Blaschke <code@tobiasblaschke.de>
|
|
* @since 2013-10-12
|
|
*/
|
|
public class Intent implements ContextItem, Comparable<Intent> {
|
|
/**
|
|
* Key into the Context that represents the Intent.
|
|
*/
|
|
public static final ContextKey INTENT_KEY = new ContextKey() {};
|
|
public enum IntentType {
|
|
/** The target could not be Identified for sure */
|
|
UNKNOWN_TARGET,
|
|
/** The Class the Intent Action refers to belongs to this App */
|
|
INTERNAL_TARGET,
|
|
/** The Class the Intent Action refers to belongs to an external App */
|
|
EXTERNAL_TARGET,
|
|
/** The Action is a Constant defined on the Android-Reference Manual */
|
|
STANDARD_ACTION,
|
|
SYSTEM_SERVICE,
|
|
/** So External and maybe internal */
|
|
BROADCAST,
|
|
/** Do not handle intent */
|
|
IGNORE
|
|
}
|
|
|
|
private enum Explicit {
|
|
UNSET,
|
|
IMPLICIT,
|
|
EXPLICIT,
|
|
/** An other target was set for an explicit Intent */
|
|
MULTI
|
|
}
|
|
|
|
public static final Atom UNBOUND = Atom.findOrCreateAsciiAtom("Unbound");
|
|
|
|
private Atom action;
|
|
public Atom uri;
|
|
private IntentType type;
|
|
private Explicit explicit = Explicit.UNSET;
|
|
private AndroidComponent targetCompontent; // Activity, Service, ...
|
|
private boolean immutable = false;
|
|
|
|
public Intent() {
|
|
this.action = null;
|
|
}
|
|
public Intent(String action) {
|
|
this(Atom.findOrCreateAsciiAtom(action));
|
|
}
|
|
public Intent(Atom action) {
|
|
this(action, null);
|
|
}
|
|
public Intent(Atom action, Atom uri) {
|
|
this.action = action;
|
|
this.uri = uri;
|
|
this.type = null; // Delay computation upon it's need
|
|
this.targetCompontent = null;
|
|
}
|
|
public Intent(TypeName action, Atom uri) {
|
|
this(Atom.findOrCreateAsciiAtom(action.toString()), uri);
|
|
}
|
|
public Intent(TypeName action) {
|
|
this(action, null);
|
|
}
|
|
|
|
public void setExplicit() {
|
|
switch (explicit) {
|
|
case UNSET:
|
|
// TODO: This is dangerous?
|
|
explicit = Explicit.EXPLICIT;
|
|
break;
|
|
case EXPLICIT:
|
|
|
|
unbind();
|
|
}
|
|
}
|
|
|
|
public boolean isExplicit() {
|
|
return explicit == Explicit.EXPLICIT;
|
|
}
|
|
|
|
public void setImmutable() {
|
|
this.immutable = true;
|
|
}
|
|
|
|
@Override
|
|
public Intent clone() {
|
|
final Intent clone = new Intent();
|
|
clone.action = this.action; // OK here?
|
|
clone.uri = this.uri;
|
|
clone.type = this.type;
|
|
clone.explicit = this.explicit;
|
|
clone.targetCompontent = this.targetCompontent;
|
|
return clone;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the explicit target of the intent.
|
|
*
|
|
* If setAction is called multible times on an Intent it becomes UNBOUND.
|
|
*/
|
|
public void setActionExplicit(Atom action) {
|
|
if (action == null) {
|
|
throw new IllegalArgumentException("Action may not be null!");
|
|
}
|
|
|
|
if (this.action == null) {
|
|
assert (this.explicit == Explicit.UNSET) : "No Action but Intent is not UNSET - is " + this.explicit;
|
|
assert (! immutable) : "Intent was marked immutable - can't change it.";
|
|
this.action = action;
|
|
this.explicit = Explicit.EXPLICIT;
|
|
} else if (isExplicit() && (! this.action.equals(action))) {
|
|
// We already have the explicit target. Ignore the change.
|
|
|
|
unbind();
|
|
} else if (! isExplicit() ) {
|
|
|
|
assert (! immutable) : "Intent was marked immutable - can't change it.";
|
|
this.action = action;
|
|
this.explicit = Explicit.EXPLICIT;
|
|
// TODO: Set type?
|
|
} else {
|
|
// Set to same values - OK
|
|
}
|
|
}
|
|
|
|
|
|
public void unbind() {
|
|
assert (! immutable) : "Intent was marked immutable - can't change it.";
|
|
this.action = UNBOUND;
|
|
this.type = IntentType.UNKNOWN_TARGET;
|
|
this.explicit = Explicit.MULTI; // XXX shoulb we do this?
|
|
}
|
|
|
|
/**
|
|
* Set the target of the intent.
|
|
*
|
|
* If setAction is called multible times on an Intent it becomes UNBOUND.
|
|
*/
|
|
public void setAction(Atom action) {
|
|
if (this.action == null) {
|
|
assert (! immutable) : "Intent was marked immutable - can't change it.";
|
|
this.action = action;
|
|
} else if (isExplicit()) {
|
|
// We already have the explicit target. Ignore the change.
|
|
} else if (! action.equals(this.action)) {
|
|
unbind();
|
|
}
|
|
}
|
|
|
|
public Atom getAction() {
|
|
if (this.action == null) {
|
|
assert (! isExplicit()) : "Beeing explicit implies having an action!";
|
|
return UNBOUND;
|
|
}
|
|
return this.action;
|
|
}
|
|
|
|
public IntentType getType() {
|
|
if (this.type != null) {
|
|
return this.type;
|
|
} else {
|
|
if (isSystemService(this)) {
|
|
this.type = IntentType.SYSTEM_SERVICE;
|
|
} else if (isStandardAction(this)) {
|
|
this.type = IntentType.STANDARD_ACTION;
|
|
} else if (isInternal(this)) {
|
|
this.type = IntentType.INTERNAL_TARGET;
|
|
} else if (isExternal(this)) {
|
|
this.type = IntentType.EXTERNAL_TARGET;
|
|
} else {
|
|
this.type = IntentType.UNKNOWN_TARGET;
|
|
}
|
|
}
|
|
return this.type;
|
|
}
|
|
|
|
/**
|
|
* Return the type of Component associated with this Intent.
|
|
*
|
|
* May return null (especially on an UNKNOWN target). The IntentContextInterpreter uses the
|
|
* IntentStarters.StartInfo to determine the Target. However it is nicer to set the Component
|
|
* here.
|
|
*
|
|
* @todo Set the Component somewhere
|
|
*/
|
|
public AndroidComponent getComponent() {
|
|
return this.targetCompontent;
|
|
}
|
|
|
|
private static boolean isSystemService(Intent intent) {
|
|
assert (intent.action != null);
|
|
return (intent.action.getVal(0) != 'L') && (intent.action.rIndex((byte) '/') < 0) && (intent.action.rIndex((byte) '.') < 0);
|
|
}
|
|
|
|
/**
|
|
* Fallback: tries to determine on the Intent itself if it's internal.
|
|
*
|
|
* Use {@link #isInternal(boolean)} instead.
|
|
*
|
|
* Recomputes if the Intent is internal.
|
|
* TODO:
|
|
* @param intent
|
|
* @todo Implement it ;)
|
|
* @todo What to return if it does not, but Summary-Information is available?
|
|
* @todo We should read in the Manifest.xml rather than relying on the packet name!
|
|
*/
|
|
private static boolean isInternal(Intent intent) { // XXX: This may loop forever!
|
|
/*final Intent override = AndroidEntryPointManager.MANAGER.getIntent(intent);
|
|
|
|
logger.warn("Intent.isInternal(Intent) is an unsafe fallback!");
|
|
|
|
if (override.getType() != IntentType.UNKNOWN_TARGET) {
|
|
return override.isInternal(true); // The isInternal defined later not this one!
|
|
}*/
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Fallback: tries to determine on the Intent itself if it's external.
|
|
*
|
|
* Use {@link #isExternal(boolean)} instead.
|
|
*/
|
|
private static boolean isExternal(Intent intent) { // XXX: This may loop forever!
|
|
/*final Intent override = AndroidEntryPointManager.MANAGER.getIntent(intent);
|
|
|
|
logger.warn("Intent.isExternal(Intent) is an unsafe fallback!");
|
|
|
|
if (override.getType() != IntentType.UNKNOWN_TARGET) {
|
|
return override.isExternal(true); // The isExternal defined later not this one!
|
|
}*/
|
|
|
|
if ((intent.action == null ) || (intent.action.equals(UNBOUND))) {
|
|
return false; // Is Unknown
|
|
}
|
|
|
|
String pack = AndroidEntryPointManager.MANAGER.guessPackage();
|
|
|
|
|
|
if (pack == null) {
|
|
// Unknown so not selected as external
|
|
return false;
|
|
}
|
|
return (! (intent.action.toString().startsWith("L" + pack) || intent.action.toString().startsWith(pack)));
|
|
}
|
|
|
|
/**
|
|
* Fallback: tries to determine on the Intent itself if it's a standard action.
|
|
*/
|
|
private static boolean isStandardAction(Intent intent) { //TODO: This may loop forever!
|
|
/*final Intent override = AndroidEntryPointManager.MANAGER.getIntent(intent);
|
|
|
|
logger.warn("Intent.isStandardAction(Intent) is an unsafe fallback!");
|
|
|
|
if (override.getType() != IntentType.UNKNOWN_TARGET) {
|
|
return override.isStandard(true);
|
|
}*/
|
|
if ((intent.action == null ) || (intent.action.equals(UNBOUND))) {
|
|
return false; // Is Unknown
|
|
}
|
|
|
|
// TODO: Make this static or so
|
|
final Atom andoidIntentAction = Atom.findOrCreateAsciiAtom("Landroid/intent/action");
|
|
return intent.action.startsWith(andoidIntentAction);
|
|
}
|
|
|
|
/**
|
|
* Is the Intents target internally resolvable.
|
|
*
|
|
* @return if the Intent is associated to a class in the analyzed application.
|
|
* @param strict if false return unknown target as internal
|
|
*/
|
|
public boolean isInternal(boolean strict) {
|
|
IntentType type = getType(); // Asserts type is computed
|
|
|
|
return ((type == IntentType.INTERNAL_TARGET) || ((! strict) && (type == IntentType.UNKNOWN_TARGET)));
|
|
}
|
|
|
|
/**
|
|
* Has the target to be resolved by an external App.
|
|
*
|
|
* The Intent is not associated to a class in this application or it's a Standard
|
|
* action defined in the Android Reference Manual.
|
|
*
|
|
* @param strict if false return unknown target as external
|
|
*/
|
|
public boolean isExternal(boolean strict) {
|
|
IntentType type = getType(); // Asserts type is computed
|
|
|
|
return ((type == IntentType.EXTERNAL_TARGET) || (type == IntentType.STANDARD_ACTION)
|
|
|| ((! strict) && (type == IntentType.UNKNOWN_TARGET)));
|
|
}
|
|
|
|
/**
|
|
* Is the Intent one of the System-Defined ones.
|
|
*
|
|
* It's a Standard action defined in the Android Reference Manual. Implies isExternal.
|
|
*
|
|
* @param strict if false return unknown target as standard
|
|
*/
|
|
public boolean isStandard(boolean strict) {
|
|
IntentType type = getType(); // Asserts type is computed
|
|
|
|
return ((type == IntentType.STANDARD_ACTION) || ((! strict) && (type == IntentType.UNKNOWN_TARGET)));
|
|
}
|
|
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuffer ret;
|
|
if ((this.action == null) || (this.action.equals(UNBOUND))) {
|
|
return "Unbound Intent";
|
|
} else if (getType() == IntentType.SYSTEM_SERVICE) {
|
|
ret = new StringBuffer("SystemService(");
|
|
} else {
|
|
ret = new StringBuffer("Intent(");
|
|
}
|
|
ret.append(this.action.toString());
|
|
if (uri != null) {
|
|
ret.append(", ");
|
|
ret.append(this.uri.toString());
|
|
}
|
|
ret.append(") of type ");
|
|
ret.append(getType());
|
|
return ret.toString();
|
|
}
|
|
|
|
/**
|
|
* CLASHES: Does not consider intent-type.
|
|
*
|
|
* This clash is however intended: This aids in resolving the override of an Intent.
|
|
* The AndroidEntryPointManager generates new Intent Objects. Instead of searching all
|
|
* overrides we get it for free.
|
|
*/
|
|
@Override
|
|
public int hashCode() {
|
|
// DO NOT USE TYPE!
|
|
if (this.uri != null) {
|
|
return getAction().hashCode() * this.uri.hashCode();
|
|
} else {
|
|
return getAction().hashCode();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Does not consider the associated URI.
|
|
*/
|
|
public boolean equalAction(Intent other) {
|
|
return getAction().equals(other.getAction());
|
|
}
|
|
|
|
/**
|
|
* Intents are equal to Intents with other type.
|
|
*
|
|
* This clash is however intended: This aids in resolving the override of an Intent.
|
|
* The AndroidEntryPointManager generates new Intent Objects. Instead of searching all
|
|
* overrides we get it for free.
|
|
*/
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (o instanceof Intent) {
|
|
Intent other = (Intent) o;
|
|
|
|
// DO NOT USE TYPE!
|
|
if (this.uri != null) {
|
|
return ( (this.uri.equals(other.uri)) && equalAction(other) ); // && (this.explicit == other.explicit));
|
|
} else {
|
|
return ( (other.uri == null) && equalAction(other) ); // && (this.explicit == other.explicit)) ;
|
|
}
|
|
} else {
|
|
System.err.println("WARNING: Can't compare Intent to " + o.getClass());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public Intent resolve() {
|
|
return AndroidEntryPointManager.MANAGER.getIntent(this);
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(Intent other) {
|
|
return getAction().toString().compareTo(other.getAction().toString());
|
|
}
|
|
}
|