/*
 * Copyright (C) 2009 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 okhttp3.internal.huc;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.OkHttpClient;
import okhttp3.OkUrlFactory;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.Internal;
import okhttp3.internal.cache.CacheRequest;
import okhttp3.internal.cache.CacheStrategy;
import okhttp3.internal.cache.InternalCache;
import org.junit.Ignore;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
 * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI, HttpURLConnection recovers
 * from URLs with unescaped but unsupported URI characters like '{' and '|' by escaping these
 * characters.
 */
public final class URLEncodingTest {
  /**
   * This test goes through the exhaustive set of interesting ASCII characters because most of those
   * characters are interesting in some way according to RFC 2396 and RFC 2732. http://b/1158780
   */
  @Test @Ignore public void lenientUrlToUri() throws Exception {
    // alphanum
    testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09");

    // control characters
    testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01");
    testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F");

    // ascii characters
    testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
    testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
    testUrlToUriMapping(" ", "%20", "%20", "%20", "%20");
    testUrlToUriMapping("!", "!", "!", "!", "!");
    testUrlToUriMapping("\"", "%22", "%22", "%22", "%22");
    testUrlToUriMapping("#", null, null, null, "%23");
    testUrlToUriMapping("$", "$", "$", "$", "$");
    testUrlToUriMapping("&", "&", "&", "&", "&");
    testUrlToUriMapping("'", "'", "'", "'", "'");
    testUrlToUriMapping("(", "(", "(", "(", "(");
    testUrlToUriMapping(")", ")", ")", ")", ")");
    testUrlToUriMapping("*", "*", "*", "*", "*");
    testUrlToUriMapping("+", "+", "+", "+", "+");
    testUrlToUriMapping(",", ",", ",", ",", ",");
    testUrlToUriMapping("-", "-", "-", "-", "-");
    testUrlToUriMapping(".", ".", ".", ".", ".");
    testUrlToUriMapping("/", null, "/", "/", "/");
    testUrlToUriMapping(":", null, ":", ":", ":");
    testUrlToUriMapping(";", ";", ";", ";", ";");
    testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C");
    testUrlToUriMapping("=", "=", "=", "=", "=");
    testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E");
    testUrlToUriMapping("?", null, null, "?", "?");
    testUrlToUriMapping("@", "@", "@", "@", "@");
    testUrlToUriMapping("[", null, "%5B", null, "%5B");
    testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C");
    testUrlToUriMapping("]", null, "%5D", null, "%5D");
    testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E");
    testUrlToUriMapping("_", "_", "_", "_", "_");
    testUrlToUriMapping("`", "%60", "%60", "%60", "%60");
    testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B");
    testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C");
    testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D");
    testUrlToUriMapping("~", "~", "~", "~", "~");
    testUrlToUriMapping("~", "~", "~", "~", "~");
    testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F");

    // beyond ascii
    testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80");
    testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac");
    testUrlToUriMapping("\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f",
        "\ud842\udf9f");
  }

  @Test @Ignore public void lenientUrlToUriNul() throws Exception {
    testUrlToUriMapping("\u0000", "%00", "%00", "%00", "%00"); // RI fails this
  }

  private void testUrlToUriMapping(String string, String asAuthority, String asFile, String asQuery,
      String asFragment) throws Exception {
    if (asAuthority != null) {
      assertEquals("http://host" + asAuthority + ".tld/",
          backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString());
    }
    if (asFile != null) {
      assertEquals("http://host.tld/file" + asFile + "/",
          backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString());
    }
    if (asQuery != null) {
      assertEquals("http://host.tld/file?q" + asQuery + "=x",
          backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString());
    }
    assertEquals("http://host.tld/file#" + asFragment + "-x",
        backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
  }

  private URI backdoorUrlToUri(URL url) throws Exception {
    final AtomicReference<URI> uriReference = new AtomicReference<>();

    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    Internal.instance.setCache(builder, new InternalCache() {
      @Override public Response get(Request request) throws IOException {
        uriReference.set(request.url().uri());
        throw new UnsupportedOperationException();
      }

      @Override public CacheRequest put(Response response) throws IOException {
        return null;
      }

      @Override public void remove(Request request) throws IOException {
      }

      @Override public void update(Response cached, Response network) {
      }

      @Override public void trackConditionalCacheHit() {
      }

      @Override public void trackResponse(CacheStrategy cacheStrategy) {
      }
    });

    try {
      HttpURLConnection connection = new OkUrlFactory(builder.build()).open(url);
      connection.getResponseCode();
    } catch (Exception expected) {
      if (expected.getCause() instanceof URISyntaxException) {
        expected.printStackTrace();
      }
    }

    return uriReference.get();
  }
}
