resolved conflicts for merge of 51f523b9 to lmp-mr1-dev-plus-aosp

Change-Id: I6408a8ed19f3d676d1b0da3e4f7f29541b5d4dc8
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/tls/HostnameVerifierTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/tls/HostnameVerifierTest.java
index 82b1952..fcaa9c0 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/tls/HostnameVerifierTest.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/tls/HostnameVerifierTest.java
@@ -289,7 +289,7 @@
         + "G9Z6tyMbmfRY+dLSh3a9JwoEcBUso6EWYBakLbq4nG/nvYdYvG9ehrnLVwZFL82e\n"
         + "l3Q/RK95bnA6cuRClGusLad0e6bjkBzx/VQ3VarDEpAkTLUGVAa0CLXtnyc=\n"
         + "-----END CERTIFICATE-----\n");
-    assertTrue(verifier.verify("foo.com", session));
+    assertFalse(verifier.verify("foo.com", session));
     assertTrue(verifier.verify("www.foo.com", session));
     assertTrue(verifier.verify("\u82b1\u5b50.foo.com", session));
     assertFalse(verifier.verify("a.b.foo.com", session));
@@ -511,7 +511,7 @@
     assertFalse(verifier.verify("foo.com", session));
     assertTrue(verifier.verify("bar.com", session));
     assertTrue(verifier.verify("a.baz.com", session));
-    assertTrue(verifier.verify("baz.com", session));
+    assertFalse(verifier.verify("baz.com", session));
     assertFalse(verifier.verify("a.foo.com", session));
     assertFalse(verifier.verify("a.bar.com", session));
     assertFalse(verifier.verify("quux.com", session));
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/tls/OkHostnameVerifier.java b/okhttp/src/main/java/com/squareup/okhttp/internal/tls/OkHostnameVerifier.java
index 6f4ea99..10dbd93 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/tls/OkHostnameVerifier.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/tls/OkHostnameVerifier.java
@@ -58,6 +58,7 @@
   private OkHostnameVerifier() {
   }
 
+  @Override
   public boolean verify(String host, SSLSession session) {
     try {
       Certificate[] certificates = session.getPeerCertificates();
@@ -144,48 +145,97 @@
   }
 
   /**
-   * Returns true if {@code hostName} matches the name or pattern {@code cn}.
+   * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern}.
    *
-   * @param hostName lowercase host name.
-   * @param cn certificate host name. May include wildcards like
-   *     {@code *.android.com}.
+   * @param hostName lower-case host name.
+   * @param pattern domain name pattern from certificate. May be a wildcard pattern such as
+   *        {@code *.android.com}.
    */
-  public boolean verifyHostName(String hostName, String cn) {
+  private boolean verifyHostName(String hostName, String pattern) {
+    // Basic sanity checks
     // Check length == 0 instead of .isEmpty() to support Java 5.
-    if (hostName == null || hostName.length() == 0 || cn == null || cn.length() == 0) {
+    if ((hostName == null) || (hostName.length() == 0) || (hostName.startsWith("."))
+        || (hostName.endsWith(".."))) {
+      // Invalid domain name
+      return false;
+    }
+    if ((pattern == null) || (pattern.length() == 0) || (pattern.startsWith("."))
+        || (pattern.endsWith(".."))) {
+      // Invalid pattern/domain name
       return false;
     }
 
-    cn = cn.toLowerCase(Locale.US);
+    // Normalize hostName and pattern by turning them into absolute domain names if they are not
+    // yet absolute. This is needed because server certificates do not normally contain absolute
+    // names or patterns, but they should be treated as absolute. At the same time, any hostName
+    // presented to this method should also be treated as absolute for the purposes of matching
+    // to the server certificate.
+    //   www.android.com  matches www.android.com
+    //   www.android.com  matches www.android.com.
+    //   www.android.com. matches www.android.com.
+    //   www.android.com. matches www.android.com
+    if (!hostName.endsWith(".")) {
+      hostName += '.';
+    }
+    if (!pattern.endsWith(".")) {
+      pattern += '.';
+    }
+    // hostName and pattern are now absolute domain names.
 
-    if (!cn.contains("*")) {
-      return hostName.equals(cn);
+    pattern = pattern.toLowerCase(Locale.US);
+    // hostName and pattern are now in lower case -- domain names are case-insensitive.
+
+    if (!pattern.contains("*")) {
+      // Not a wildcard pattern -- hostName and pattern must match exactly.
+      return hostName.equals(pattern);
+    }
+    // Wildcard pattern
+
+    // WILDCARD PATTERN RULES:
+    // 1. Asterisk (*) is only permitted in the left-most domain name label and must be the
+    //    only character in that label (i.e., must match the whole left-most label).
+    //    For example, *.example.com is permitted, while *a.example.com, a*.example.com,
+    //    a*b.example.com, a.*.example.com are not permitted.
+    // 2. Asterisk (*) cannot match across domain name labels.
+    //    For example, *.example.com matches test.example.com but does not match
+    //    sub.test.example.com.
+    // 3. Wildcard patterns for single-label domain names are not permitted.
+
+    if ((!pattern.startsWith("*.")) || (pattern.indexOf('*', 1) != -1)) {
+      // Asterisk (*) is only permitted in the left-most domain name label and must be the only
+      // character in that label
+      return false;
     }
 
-    if (cn.startsWith("*.") && hostName.equals(cn.substring(2))) {
-      return true; // "*.foo.com" matches "foo.com"
+    // Optimization: check whether hostName is too short to match the pattern. hostName must be at
+    // least as long as the pattern because asterisk must match the whole left-most label and
+    // hostName starts with a non-empty label. Thus, asterisk has to match one or more characters.
+    if (hostName.length() < pattern.length()) {
+      // hostName too short to match the pattern.
+      return false;
     }
 
-    int asterisk = cn.indexOf('*');
-    int dot = cn.indexOf('.');
-    if (asterisk > dot) {
-      return false; // malformed; wildcard must be in the first part of the cn
+    if ("*.".equals(pattern)) {
+      // Wildcard pattern for single-label domain name -- not permitted.
+      return false;
     }
 
-    if (!hostName.regionMatches(0, cn, 0, asterisk)) {
-      return false; // prefix before '*' doesn't match
+    // hostName must end with the region of pattern following the asterisk.
+    String suffix = pattern.substring(1);
+    if (!hostName.endsWith(suffix)) {
+      // hostName does not end with the suffix
+      return false;
     }
 
-    int suffixLength = cn.length() - (asterisk + 1);
-    int suffixStart = hostName.length() - suffixLength;
-    if (hostName.indexOf('.', asterisk) < suffixStart) {
-      return false; // wildcard '*' can't match a '.'
+    // Check that asterisk did not match across domain name labels.
+    int suffixStartIndexInHostName = hostName.length() - suffix.length();
+    if ((suffixStartIndexInHostName > 0)
+        && (hostName.lastIndexOf('.', suffixStartIndexInHostName - 1) != -1)) {
+      // Asterisk is matching across domain name labels -- not permitted.
+      return false;
     }
 
-    if (!hostName.regionMatches(suffixStart, cn, asterisk + 1, suffixLength)) {
-      return false; // suffix after '*' doesn't match
-    }
-
+    // hostName matches pattern
     return true;
   }
 }