content: /*
   * Copyright (C) 2016 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.wifi.hotspot2;
  
  import android.text.TextUtils;
  
  import com.android.server.wifi.hotspot2.Utils;
  
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Utility class for matching domain names.
   */
  public class DomainMatcher {
      public static final int MATCH_NONE = 0;
      public static final int MATCH_PRIMARY = 1;
      public static final int MATCH_SECONDARY = 2;
  
      /**
       * The root of the Label tree.
       */
      private final Label mRoot;
  
      /**
       * Label tree representation for the domain name.  Labels are delimited by "." in the domain
       * name.
       *
       * For example, the tree representation of "android.google.com" as a primary domain:
       * [com, None] -> [google, None] -> [android, Primary]
       *
       */
      private static class Label {
          private final Map<String, Label> mSubDomains;
          private int mMatch;
  
          Label(int match) {
              mMatch = match;
              mSubDomains = new HashMap<String, Label>();
          }
  
          /**
           * Add sub-domains to this label.
           *
           * @param labels The iterator of domain label strings
           * @param match The match status of the domain
           */
          public void addDomain(Iterator<String> labels, int match) {
              String labelName = labels.next();
              // Create the Label object if it doesn't exist yet.
              Label subLabel = mSubDomains.get(labelName);
              if (subLabel == null) {
                  subLabel = new Label(MATCH_NONE);
                  mSubDomains.put(labelName, subLabel);
              }
  
              if (labels.hasNext()) {
                  // Adding sub-domain.
                  subLabel.addDomain(labels, match);
              } else {
                  // End of the domain, update the match status.
                  subLabel.mMatch = match;
              }
          }
  
          /**
           * Return the Label for the give label string.
           * @param labelString The label string to look for
           * @return {@link Label}
           */
          public Label getSubLabel(String labelString) {
              return mSubDomains.get(labelString);
          }
  
          /**
           * Return the match status
           *
           * @return The match status
           */
          public int getMatch() {
              return mMatch;
          }
  
          private void toString(StringBuilder sb) {
              if (mSubDomains != null) {
                  sb.append(".{");
                  for (Map.Entry<String, Label> entry : mSubDomains.entrySet()) {
                      sb.append(entry.getKey());
                      entry.getValue().toString(sb);
                  }
                  sb.append('}');
              } else {
                  sb.append('=').append(mMatch);
              }
          }
  
          @Override
          public String toString() {
              StringBuilder sb = new StringBuilder();
              toString(sb);
              return sb.toString();
          }
      }
  
      public DomainMatcher(String primaryDomain, List<String> secondaryDomains) {
          // Create the root label.
          mRoot = new Label(MATCH_NONE);
  
          // Add secondary domains.
          if (secondaryDomains != null) {
              for (String domain : secondaryDomains) {
                  if (!TextUtils.isEmpty(domain)) {
                      List<String> secondaryLabel = Utils.splitDomain(domain);
                      mRoot.addDomain(secondaryLabel.iterator(), MATCH_SECONDARY);
                  }
              }
          }
  
          // Add primary domain, primary overwrites secondary.
          if (!TextUtils.isEmpty(primaryDomain)) {
              List<String> primaryLabel = Utils.splitDomain(primaryDomain);
              mRoot.addDomain(primaryLabel.iterator(), MATCH_PRIMARY);
          }
      }
  
      /**
       * Check if domain is either the same or a sub-domain of any of the domains in the
       * domain tree in this matcher, i.e. all or a sub-set of the labels in domain matches
       * a path in the tree.
       *
       * This will have precedence for matching primary domain over secondary domain if both
       * are found.
       *
       * For example, with primary domain set to "test.google.com" and secondary domain set to
       * "google.com":
       * "test2.test.google.com" -> Match.Primary
       * "test1.google.com" -> Match.Secondary
       *
       * @param domainName Domain name to be checked.
       * @return The match status
       */
      public int isSubDomain(String domainName) {
          if (TextUtils.isEmpty(domainName)) {
              return MATCH_NONE;
          }
          List<String> domainLabels = Utils.splitDomain(domainName);
  
          Label label = mRoot;
          int match = MATCH_NONE;
          for (String labelString : domainLabels) {
              label = label.getSubLabel(labelString);
              if (label == null) {
                  break;
              } else if (label.getMatch() != MATCH_NONE) {
                  match = label.getMatch();
                  if (match == MATCH_PRIMARY) {
                      break;
                  }
              }
          }
          return match;
      }
  
      /**
       * Check if domain2 is a sub-domain of domain1.
       *
       * @param domain1 The string of the first domain
       * @param domain2 The string of the second domain
       * @return true if the second domain is the sub-domain of the first
       */
      public static boolean arg2SubdomainOfArg1(String domain1, String domain2) {
          if (TextUtils.isEmpty(domain1) || TextUtils.isEmpty(domain2)) {
              return false;
          }
  
          List<String> labels1 = Utils.splitDomain(domain1);
          List<String> labels2 = Utils.splitDomain(domain2);
  
          // domain2 must be the same or longer than domain1 in order to be a sub-domain.
          if (labels2.size() < labels1.size()) {
              return false;
          }
  
          Iterator<String> l1 = labels1.iterator();
          Iterator<String> l2 = labels2.iterator();
  
          while(l1.hasNext()) {
              if (!TextUtils.equals(l1.next(), l2.next())) {
                  return false;
              }
          }
          return true;
      }
  
      @Override
      public String toString() {
          return "Domain matcher " + mRoot;
      }
  }
  