All Downloads are FREE. Search and download functionalities are using the official Maven repository.

soot.jimple.infoflow.android.source.parsers.xml.AbstractXMLSourceSinkParser Maven / Gradle / Ivy

package soot.jimple.infoflow.android.source.parsers.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import soot.jimple.infoflow.android.data.AndroidMethod;
import soot.jimple.infoflow.android.data.CategoryDefinition;
import soot.jimple.infoflow.android.data.CategoryDefinition.CATEGORY;
import soot.jimple.infoflow.data.AbstractMethodAndClass;
import soot.jimple.infoflow.sourcesSinks.definitions.AccessPathTuple;
import soot.jimple.infoflow.sourcesSinks.definitions.IAccessPathBasedSourceSinkDefinition;
import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkCategory;
import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition;
import soot.jimple.infoflow.sourcesSinks.definitions.MethodSourceSinkDefinition;
import soot.jimple.infoflow.sourcesSinks.definitions.MethodSourceSinkDefinition.CallType;
import soot.jimple.infoflow.sourcesSinks.definitions.SourceSinkType;

/**
 * Abstract class for all Flowdroid XML parsers. Returns a Set of Methods when
 * calling the function parse. Subclasses may implement parser specifics to
 * different language engines.
 * 
 * @author Anna-Katharina Wickert
 * @author Joern Tillmans
 * @author Steven Arzt
 * @author Niklas Vogel
 *
 */
public abstract class AbstractXMLSourceSinkParser {
	private final static Logger logger = LoggerFactory.getLogger(AbstractXMLSourceSinkParser.class);

	/**
	 * Filter interface for excluding certain categories from the source/sink set.
	 * 
	 * @author Steven Arzt
	 *
	 */
	public interface ICategoryFilter {

		/**
		 * Checks whether methods from the given category shall be parsed or not
		 * 
		 * @param category The category in which the source or sink is defined
		 * @return True if the given category shall be parsed, otherwise false
		 */
		public boolean acceptsCategory(ISourceSinkCategory category);

		/**
		 * Filters sources and sinks by category
		 * 
		 * @param category       The category in which the source or sink is defined
		 * @param sourceSinkType Specifies whether the category is used for sources or
		 *                       sinks
		 * @return The acceptable use of the given category. This is a reduction from
		 *         the given type. If the requested type, for example, is "Both", this
		 *         method may return "Source", if the category shall only be used for
		 *         sources, but not for sinks.
		 */
		public SourceSinkType filter(ISourceSinkCategory category, SourceSinkType sourceSinkType);

	}

	/**
	 * Handler class for the XML parser
	 * 
	 * @author Anna-Katharina Wickert
	 * @author Joern Tillmans
	 * @author Steven Arzt
	 *
	 */
	protected class SAXHandler extends DefaultHandler {

		// Holding temporary values for handling with SAX
		protected String methodSignature = null;
		protected String fieldSignature = null;

		protected ISourceSinkCategory category;
		protected boolean isSource, isSink;
		protected List pathElements;
		protected List pathElementTypes;
		protected int paramIndex;
		protected List paramTypes = new ArrayList();
		protected CallType callType;

		protected String accessPathParentElement = "";
		protected String description = "";

		protected Set baseAPs = new HashSet<>();
		protected List> paramAPs = new ArrayList<>();
		protected Set returnAPs = new HashSet<>();

		protected Map sourcesAndSinks;
		protected ICategoryFilter categoryFilter = null;

		public SAXHandler() {
		}

		public SAXHandler(ICategoryFilter categoryFilter) {
			this.categoryFilter = categoryFilter;
		}

		/**
		 * Event Handler for the starting element for SAX. Possible start elements for
		 * filling AndroidMethod objects with the new data format: - method: Setting
		 * parsingvalues to false or null and get and set the signature and category, -
		 * accessPath: To get the information whether the AndroidMethod is a sink or
		 * source, - and the other elements doesn't care for creating the AndroidMethod
		 * object. At these element we will look, if calling the method getAccessPath
		 * (using an new SAX Handler)
		 */
		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes)
				throws SAXException {
			String qNameLower = qName.toLowerCase();
			switch (qNameLower) {
			case XMLConstants.CATEGORY_TAG:
				handleStarttagCategory(attributes);
				break;

			case XMLConstants.FIELD_TAG:
				handleStarttagField(attributes);
				break;

			case XMLConstants.METHOD_TAG:
				handleStarttagMeethod(attributes);
				break;

			case XMLConstants.ACCESSPATH_TAG:
				handleStarttagAccesspath(attributes);
				break;

			case XMLConstants.BASE_TAG:
				accessPathParentElement = qNameLower;
				description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE);
				break;

			case XMLConstants.RETURN_TAG:
				accessPathParentElement = qNameLower;
				description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE);
				break;

			case XMLConstants.PARAM_TAG:
				handleStarttagParam(attributes, qNameLower);
				break;

			case XMLConstants.PATHELEMENT_TAG:
				handleStarttagPathelement(attributes);
				break;
			}
		}

		protected void handleStarttagCategory(Attributes attributes) {
			String strSysCategory = attributes.getValue(XMLConstants.ID_ATTRIBUTE).trim();
			String strCustomCategory = attributes.getValue(XMLConstants.CUSTOM_ID_ATTRIBUTE);
			if (strCustomCategory != null && !strCustomCategory.isEmpty())
				strCustomCategory = strCustomCategory.trim();
			String strCustomDescription = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE);
			if (strCustomDescription != null && !strCustomDescription.isEmpty())
				strCustomDescription = strCustomDescription.trim();

			category = getOrMakeCategory(CATEGORY.valueOf(strSysCategory), strCustomCategory, strCustomDescription);

			// Check for excluded categories
			if (categoryFilter != null && !categoryFilter.acceptsCategory(category)) {
				category = null;
			}
		}

		protected void handleStarttagField(Attributes attributes) {
			if (category != null && attributes != null) {
				fieldSignature = parseSignature(attributes);
				accessPathParentElement = XMLConstants.BASE_TAG;
			}
		}

		protected void handleStarttagMeethod(Attributes attributes) {
			if (category != null && attributes != null) {
				methodSignature = parseSignature(attributes);

				// Get the call type
				callType = CallType.MethodCall;
				String strCallType = attributes.getValue(XMLConstants.CALL_TYPE);
				if (strCallType != null && !strCallType.isEmpty()) {
					strCallType = strCallType.trim();
					if (strCallType.equalsIgnoreCase("MethodCall"))
						callType = CallType.MethodCall;
					else if (strCallType.equalsIgnoreCase("Callback"))
						callType = CallType.Callback;
				}
			}
		}

		protected void handleStarttagAccesspath(Attributes attributes) {
			if ((methodSignature != null && !methodSignature.isEmpty())
					|| (fieldSignature != null && !fieldSignature.isEmpty())) {
				pathElements = new ArrayList<>();
				pathElementTypes = new ArrayList<>();

				if (attributes != null) {
					String tempStr = attributes.getValue(XMLConstants.IS_SOURCE_ATTRIBUTE);
					if (tempStr != null && !tempStr.isEmpty())
						isSource = tempStr.equalsIgnoreCase(XMLConstants.TRUE);

					tempStr = attributes.getValue(XMLConstants.IS_SINK_ATTRIBUTE);
					if (tempStr != null && !tempStr.isEmpty())
						isSink = tempStr.equalsIgnoreCase(XMLConstants.TRUE);

					String newDesc = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE);
					if (newDesc != null && !newDesc.isEmpty())
						description = newDesc;
				}
			}
		}

		protected void handleStarttagParam(Attributes attributes, String qNameLower) {
			if ((methodSignature != null || fieldSignature != null) && attributes != null) {
				String tempStr = attributes.getValue(XMLConstants.INDEX_ATTRIBUTE);
				if (tempStr != null && !tempStr.isEmpty())
					paramIndex = Integer.parseInt(tempStr);

				tempStr = attributes.getValue(XMLConstants.TYPE_ATTRIBUTE);
				if (tempStr != null && !tempStr.isEmpty())
					paramTypes.add(tempStr.trim());
			}
			accessPathParentElement = qNameLower;
			description = attributes.getValue(XMLConstants.DESCRIPTION_ATTRIBUTE);
		}

		protected void handleStarttagPathelement(Attributes attributes) {
			if (methodSignature != null && attributes != null) {
				String tempStr = attributes.getValue(XMLConstants.FIELD_ATTRIBUTE);
				if (tempStr != null && !tempStr.isEmpty()) {
					pathElements.add(tempStr);
				}

				tempStr = attributes.getValue(XMLConstants.TYPE_ATTRIBUTE);
				if (tempStr != null && !tempStr.isEmpty()) {
					pathElementTypes.add(tempStr);
				}
			}
		}

		/**
		 * Reads the method or field signature from the given attribute map
		 * 
		 * @param attributes The attribute map from which to read the signature
		 * @return The signature that identifies a Soot method or field
		 */
		private String parseSignature(Attributes attributes) {
			String signature = attributes.getValue(XMLConstants.SIGNATURE_ATTRIBUTE).trim();

			// If the user did not specify the brackets, we add them on
			// the fly
			if (signature != null && !signature.isEmpty())
				if (!signature.startsWith("<"))
					signature = "<" + signature + ">";
			return signature;
		}

		/**
		 * PathElement is the only element having values inside, so nothing to do here.
		 * Doesn't care at the current state of parsing.
		 **/
		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
		}

		/**
		 * EventHandler for the End of an element. Putting the values into the objects.
		 * For additional information: startElement description. Starting with the
		 * innerst elements and switching up to the outer elements
		 * 
		 * - pathElement -> means field sensitive, adding SootFields
		 */
		@Override
		public void endElement(String uri, String localName, String qName) throws SAXException {
			String qNameLower = qName.toLowerCase();
			switch (qNameLower) {

			case XMLConstants.CATEGORY_TAG:
				category = null;
				break;

			case XMLConstants.METHOD_TAG:
				handleEndtagMethod();
				break;

			case XMLConstants.FIELD_TAG:
				handleEndtagField();
				break;

			case XMLConstants.ACCESSPATH_TAG:
				handleEndtagAccesspath();
				break;

			case XMLConstants.BASE_TAG:
				accessPathParentElement = "";
				break;

			case XMLConstants.RETURN_TAG:
				accessPathParentElement = "";
				break;

			case XMLConstants.PARAM_TAG:
				accessPathParentElement = "";
				paramIndex = -1;
				paramTypes.clear();
				break;

			case XMLConstants.PATHELEMENT_TAG:
				break;
			}
		}

		protected void handleEndtagMethod() {
			if (methodSignature == null)
				return;

			// Check whether we have data
			if (!baseAPs.isEmpty() || !paramAPs.isEmpty() || !returnAPs.isEmpty()) {
				AndroidMethod tempMeth = AndroidMethod.createFromSignature(methodSignature);

				@SuppressWarnings("unchecked")
				ISourceSinkDefinition ssd = createMethodSourceSinkDefinition(tempMeth, baseAPs,
						paramAPs.toArray(new Set[paramAPs.size()]), returnAPs, callType, category);
				addSourceSinkDefinition(methodSignature, ssd);
			}

			// Start a new method and discard our old data
			methodSignature = null;
			fieldSignature = null;
			baseAPs = new HashSet<>();
			paramAPs = new ArrayList<>();
			returnAPs = new HashSet<>();
			description = null;
		}

		protected void handleEndtagField() {
			// Create the field source
			if (!baseAPs.isEmpty()) {
				ISourceSinkDefinition ssd = createFieldSourceSinkDefinition(fieldSignature, baseAPs, paramAPs);
				ssd.setCategory(category);
				addSourceSinkDefinition(fieldSignature, ssd);
			}

			// Start a new field and discard our old data
			methodSignature = null;
			fieldSignature = null;
			baseAPs = new HashSet<>();
			paramAPs = new ArrayList<>();
			returnAPs = new HashSet<>();
			description = null;
		}

		protected void handleEndtagAccesspath() {
			// Record the access path for the current element
			if (isSource || isSink) {
				// Clean up the path elements
				if (pathElements != null && pathElements.isEmpty() && pathElementTypes != null
						&& pathElementTypes.isEmpty()) {
					pathElements = null;
					pathElementTypes = null;
				}

				// Sanity check
				if (pathElements != null && pathElementTypes != null && pathElements.size() != pathElementTypes.size())
					throw new RuntimeException(
							String.format("Length mismatch between path elements (%d) and their types (%d)",
									pathElements.size(), pathElementTypes.size()));
				if (pathElements == null || pathElements.isEmpty())
					if (pathElementTypes != null && !pathElementTypes.isEmpty())
						throw new RuntimeException("Got types for path elements, but no elements (i.e., fields)");

				// Filter the sources and sinks
				SourceSinkType sstype = SourceSinkType.fromFlags(isSink, isSource);
				if (categoryFilter != null)
					sstype = categoryFilter.filter(category, sstype);

				if (sstype != SourceSinkType.Neither) {
					AccessPathTuple apt = AccessPathTuple.fromPathElements(pathElements, pathElementTypes, sstype);

					// Optional description
					if (description != null && !description.isEmpty())
						apt.setDescription(description);

					// Simplify the AP after setting the description for not breaking the generic
					// source definition
					apt = apt.simplify();

					switch (accessPathParentElement) {
					case XMLConstants.BASE_TAG:
						baseAPs.add(apt);
						break;
					case XMLConstants.RETURN_TAG:
						returnAPs.add(apt);
						break;
					case XMLConstants.PARAM_TAG:
						while (paramAPs.size() <= paramIndex)
							paramAPs.add(new HashSet());
						paramAPs.get(paramIndex).add(apt);
					}
				}
			}

			pathElements = null;
			pathElementTypes = null;

			isSource = false;
			isSink = false;
			pathElements = null;
			pathElementTypes = null;

			description = null;
		}

	}

	protected Map sourcesAndSinks;

	protected Set sources = new HashSet<>();
	protected Set sinks = new HashSet<>();
	protected ICategoryFilter categoryFilter = null;

	protected final Map categories = new HashMap<>();

	/**
	 * gets the category for the given values. If this is the first time such a
	 * category is requested, the respective definition is created. Otherwise, the
	 * existing one is returned.
	 * 
	 * @param systemCategory    The system-defined category name
	 * @param customCategory    The user-defined category name
	 * @param customDescription The human-readable description for the custom
	 *                          category
	 * @return The category definition object for the given category names
	 */
	private ISourceSinkCategory getOrMakeCategory(CATEGORY systemCategory, String customCategory,
			String customDescription) {
		// For the key in the map, we ignore the description. We do not want to
		// have the
		// same category id just with different descriptions.

		ISourceSinkCategory keyDef = new CategoryDefinition(systemCategory, customCategory);
		return categories.computeIfAbsent(keyDef,
				d -> new CategoryDefinition(systemCategory, customCategory, customDescription));
	}

	/**
	 * Parses the given input stream to obtain the source/sink definitions
	 * 
	 * @param stream The stream whose data to parse
	 */
	protected void parseInputStream(InputStream stream) {
		SAXParserFactory pf = SAXParserFactory.newInstance();
		try {
			pf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
			pf.setFeature("http://xml.org/sax/features/external-general-entities", false);
			pf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
			SAXParser parser = pf.newSAXParser();
			runParse(parser, stream);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}
		// Build the source and sink lists
		buildSourceSinkLists();
	}

	/**
	 * Builds the lists of sources and sinks from the data that we have parsed
	 */
	protected abstract void buildSourceSinkLists();

	/**
	 * Runs the parse method on the given parser
	 * 
	 * @param parser Parser to run parse on
	 * @param stream Input stream of XML file
	 */
	protected abstract void runParse(SAXParser parser, InputStream stream);

	/**
	 * Adds a new source or sink definition
	 * 
	 * @param signature The signature of the method or field that is considered a
	 *                  source or a sink
	 * @param ssd       The source or sink definition
	 */
	protected void addSourceSinkDefinition(String signature, IAccessPathBasedSourceSinkDefinition ssd) {
		if (sourcesAndSinks.containsKey(signature))
			sourcesAndSinks.get(signature).merge(ssd);
		else
			sourcesAndSinks.put(signature, ssd);
	}

	public Set getSources() {
		return sources;
	}

	public Set getSinks() {
		return sinks;
	}

	protected static InputStream getStream(String fileName) throws IOException {
		File f = new File(fileName);
		if (f.exists())
			return new FileInputStream(f);

		return ResourceUtils.getResourceStream(fileName);
	}

	public Set getAllMethods() {
		Set sourcesSinks = new HashSet<>(sources.size() + sinks.size());
		sourcesSinks.addAll(sources);
		sourcesSinks.addAll(sinks);
		return sourcesSinks;
	}

	protected void addSourceSinkDefinition(String signature, ISourceSinkDefinition ssd) {
		if (sourcesAndSinks.containsKey(signature))
			sourcesAndSinks.get(signature).merge(ssd);
		else
			sourcesAndSinks.put(signature, ssd);
	}

	protected abstract ISourceSinkDefinition createFieldSourceSinkDefinition(String signature,
			Set baseAPs, List> paramAPs);

	/**
	 * Factory method for {@link MethodSourceSinkDefinition} instances
	 * 
	 * @param method    The method that is to be defined as a source or sink
	 * @param baseAPs   The access paths rooted in the base object that shall be
	 *                  considered as sources or sinks
	 * @param paramAPs  The access paths rooted in parameters that shall be
	 *                  considered as sources or sinks. The index in the set
	 *                  corresponds to the index of the formal parameter to which
	 *                  the respective set of access paths belongs.
	 * @param returnAPs The access paths rooted in the return object that shall be
	 *                  considered as sources or sinks
	 * @param callType  The type of call (normal call, callback, etc.)
	 * @return The newly created {@link MethodSourceSinkDefinition} instance
	 */
	protected abstract ISourceSinkDefinition createMethodSourceSinkDefinition(AbstractMethodAndClass method,
			Set baseAPs, Set[] paramAPs, Set returnAPs,
			CallType callType, ISourceSinkCategory category);

	/**
	 * Reads the method or field signature from the given attribute map
	 * 
	 * @param attributes The attribute map from which to read the signature
	 * @return The signature that identifies a Soot method or field
	 */
	protected String parseSignature(Attributes attributes) {
		String signature = attributes.getValue(XMLConstants.SIGNATURE_ATTRIBUTE).trim();

		// If the user did not specify the brackets, we add them on
		// the fly
		if (signature != null && !signature.isEmpty())
			if (!signature.startsWith("<"))
				signature = "<" + signature + ">";
		return signature;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy