content: /*
   * 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 com.android.server.firewall;
  
  import android.content.ComponentName;
  import android.content.Intent;
  import android.net.Uri;
  import android.os.PatternMatcher;
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import java.io.IOException;
  import java.util.regex.Pattern;
  
  abstract class StringFilter implements Filter {
      private static final String ATTR_EQUALS = "equals";
      private static final String ATTR_STARTS_WITH = "startsWith";
      private static final String ATTR_CONTAINS = "contains";
      private static final String ATTR_PATTERN = "pattern";
      private static final String ATTR_REGEX = "regex";
      private static final String ATTR_IS_NULL = "isNull";
  
      private final ValueProvider mValueProvider;
  
      private StringFilter(ValueProvider valueProvider) {
          this.mValueProvider = valueProvider;
      }
  
      /**
       * Constructs a new StringFilter based on the string filter attribute on the current
       * element, and the given StringValueMatcher.
       *
       * The current node should contain exactly 1 string filter attribute. E.g. equals,
       * contains, etc. Otherwise, an XmlPullParserException will be thrown.
       *
       * @param parser      An XmlPullParser object positioned at an element that should
       *                    contain a string filter attribute
       * @return This StringFilter object
       */
      public static StringFilter readFromXml(ValueProvider valueProvider, XmlPullParser parser)
              throws IOException, XmlPullParserException {
          StringFilter filter = null;
  
          for (int i=0; i<parser.getAttributeCount(); i++) {
              StringFilter newFilter = getFilter(valueProvider, parser, i);
              if (newFilter != null) {
                  if (filter != null) {
                      throw new XmlPullParserException("Multiple string filter attributes found");
                  }
                  filter = newFilter;
              }
          }
  
          if (filter == null) {
              // if there are no string filter attributes, we default to isNull="false" so that an
              // empty filter is equivalent to an existence check
              filter = new IsNullFilter(valueProvider, false);
          }
  
          return filter;
      }
  
      private static StringFilter getFilter(ValueProvider valueProvider, XmlPullParser parser,
              int attributeIndex) {
          String attributeName = parser.getAttributeName(attributeIndex);
  
          switch (attributeName.charAt(0)) {
              case 'e':
                  if (!attributeName.equals(ATTR_EQUALS)) {
                      return null;
                  }
                  return new EqualsFilter(valueProvider, parser.getAttributeValue(attributeIndex));
              case 'i':
                  if (!attributeName.equals(ATTR_IS_NULL)) {
                      return null;
                  }
                  return new IsNullFilter(valueProvider, parser.getAttributeValue(attributeIndex));
              case 's':
                  if (!attributeName.equals(ATTR_STARTS_WITH)) {
                      return null;
                  }
                  return new StartsWithFilter(valueProvider,
                          parser.getAttributeValue(attributeIndex));
              case 'c':
                  if (!attributeName.equals(ATTR_CONTAINS)) {
                      return null;
                  }
                  return new ContainsFilter(valueProvider, parser.getAttributeValue(attributeIndex));
              case 'p':
                  if (!attributeName.equals(ATTR_PATTERN)) {
                      return null;
                  }
                  return new PatternStringFilter(valueProvider,
                          parser.getAttributeValue(attributeIndex));
              case 'r':
                  if (!attributeName.equals(ATTR_REGEX)) {
                      return null;
                  }
                  return new RegexFilter(valueProvider, parser.getAttributeValue(attributeIndex));
          }
          return null;
      }
  
      protected abstract boolean matchesValue(String value);
  
      @Override
      public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
              int callerUid, int callerPid, String resolvedType, int receivingUid) {
          String value = mValueProvider.getValue(resolvedComponent, intent, resolvedType);
          return matchesValue(value);
      }
  
      private static abstract class ValueProvider extends FilterFactory {
          protected ValueProvider(String tag) {
              super(tag);
          }
  
          public Filter newFilter(XmlPullParser parser)
                  throws IOException, XmlPullParserException {
              return StringFilter.readFromXml(this, parser);
          }
  
          public abstract String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType);
      }
  
      private static class EqualsFilter extends StringFilter {
          private final String mFilterValue;
  
          public EqualsFilter(ValueProvider valueProvider, String attrValue) {
              super(valueProvider);
              mFilterValue = attrValue;
          }
  
          @Override
          public boolean matchesValue(String value) {
              return value != null && value.equals(mFilterValue);
          }
      }
  
      private static class ContainsFilter extends StringFilter {
          private final String mFilterValue;
  
          public ContainsFilter(ValueProvider valueProvider, String attrValue) {
              super(valueProvider);
              mFilterValue = attrValue;
          }
  
          @Override
          public boolean matchesValue(String value) {
              return value != null && value.contains(mFilterValue);
          }
      }
  
      private static class StartsWithFilter extends StringFilter {
          private final String mFilterValue;
  
          public StartsWithFilter(ValueProvider valueProvider, String attrValue) {
              super(valueProvider);
              mFilterValue = attrValue;
          }
  
          @Override
          public boolean matchesValue(String value) {
              return value != null && value.startsWith(mFilterValue);
          }
      }
  
      private static class PatternStringFilter extends StringFilter {
          private final PatternMatcher mPattern;
  
          public PatternStringFilter(ValueProvider valueProvider, String attrValue) {
              super(valueProvider);
              mPattern = new PatternMatcher(attrValue, PatternMatcher.PATTERN_SIMPLE_GLOB);
          }
  
          @Override
          public boolean matchesValue(String value) {
              return value != null && mPattern.match(value);
          }
      }
  
      private static class RegexFilter extends StringFilter {
          private final Pattern mPattern;
  
          public RegexFilter(ValueProvider valueProvider, String attrValue) {
              super(valueProvider);
              this.mPattern = Pattern.compile(attrValue);
          }
  
          @Override
          public boolean matchesValue(String value) {
              return value != null && mPattern.matcher(value).matches();
          }
      }
  
      private static class IsNullFilter extends StringFilter {
          private final boolean mIsNull;
  
          public IsNullFilter(ValueProvider valueProvider, String attrValue) {
              super(valueProvider);
              mIsNull = Boolean.parseBoolean(attrValue);
          }
  
          public IsNullFilter(ValueProvider valueProvider, boolean isNull) {
              super(valueProvider);
              mIsNull = isNull;
          }
  
          @Override
          public boolean matchesValue(String value) {
              return (value == null) == mIsNull;
          }
      }
  
      public static final ValueProvider COMPONENT = new ValueProvider("component") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              if (resolvedComponent != null) {
                  return resolvedComponent.flattenToString();
              }
              return null;
          }
      };
  
      public static final ValueProvider COMPONENT_NAME = new ValueProvider("component-name") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              if (resolvedComponent != null) {
                  return resolvedComponent.getClassName();
              }
              return null;
          }
      };
  
      public static final ValueProvider COMPONENT_PACKAGE = new ValueProvider("component-package") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              if (resolvedComponent != null) {
                  return resolvedComponent.getPackageName();
              }
              return null;
          }
      };
  
      public static final FilterFactory ACTION = new ValueProvider("action") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              return intent.getAction();
          }
      };
  
      public static final ValueProvider DATA = new ValueProvider("data") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              Uri data = intent.getData();
              if (data != null) {
                  return data.toString();
              }
              return null;
          }
      };
  
      public static final ValueProvider MIME_TYPE = new ValueProvider("mime-type") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              return resolvedType;
          }
      };
  
      public static final ValueProvider SCHEME = new ValueProvider("scheme") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              Uri data = intent.getData();
              if (data != null) {
                  return data.getScheme();
              }
              return null;
          }
      };
  
      public static final ValueProvider SSP = new ValueProvider("scheme-specific-part") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              Uri data = intent.getData();
              if (data != null) {
                  return data.getSchemeSpecificPart();
              }
              return null;
          }
      };
  
      public static final ValueProvider HOST = new ValueProvider("host") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              Uri data = intent.getData();
              if (data != null) {
                  return data.getHost();
              }
              return null;
          }
      };
  
      public static final ValueProvider PATH = new ValueProvider("path") {
          @Override
          public String getValue(ComponentName resolvedComponent, Intent intent,
                  String resolvedType) {
              Uri data = intent.getData();
              if (data != null) {
                  return data.getPath();
              }
              return null;
          }
      };
  }
  