// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/basictypes.h"
#include "net/proxy/proxy_server.h"
#include "testing/gtest/include/gtest/gtest.h"

// Test the creation of ProxyServer using ProxyServer::FromURI, which parses
// inputs of the form [<scheme>"://"]<host>[":"<port>]. Verify that each part
// was labelled correctly, and the accessors all give the right data.
TEST(ProxyServerTest, FromURI) {
  const struct {
    const char* input_uri;
    const char* expected_uri;
    net::ProxyServer::Scheme expected_scheme;
    const char* expected_host;
    int expected_port;
    const char* expected_pac_string;
  } tests[] = {
    // HTTP proxy URIs:
    {
       "foopy:10",  // No scheme.
       "foopy:10",
       net::ProxyServer::SCHEME_HTTP,
       "foopy",
       10,
       "PROXY foopy:10"
    },
    {
       "http://foopy",  // No port.
       "foopy:80",
       net::ProxyServer::SCHEME_HTTP,
       "foopy",
       80,
       "PROXY foopy:80"
    },
    {
       "http://foopy:10",
       "foopy:10",
       net::ProxyServer::SCHEME_HTTP,
       "foopy",
       10,
       "PROXY foopy:10"
    },

    // IPv6 HTTP proxy URIs:
    {
       "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10",  // No scheme.
       "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10",
       net::ProxyServer::SCHEME_HTTP,
       "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210",
       10,
       "PROXY [FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10"
    },
    {
       "http://[3ffe:2a00:100:7031::1]",  // No port.
       "[3ffe:2a00:100:7031::1]:80",
       net::ProxyServer::SCHEME_HTTP,
       "3ffe:2a00:100:7031::1",
       80,
       "PROXY [3ffe:2a00:100:7031::1]:80"
    },
    {
       "http://[::192.9.5.5]",
       "[::192.9.5.5]:80",
       net::ProxyServer::SCHEME_HTTP,
       "::192.9.5.5",
       80,
       "PROXY [::192.9.5.5]:80"
    },
    {
       "http://[::FFFF:129.144.52.38]:80",
       "[::FFFF:129.144.52.38]:80",
       net::ProxyServer::SCHEME_HTTP,
       "::FFFF:129.144.52.38",
       80,
       "PROXY [::FFFF:129.144.52.38]:80"
    },

    // SOCKS4 proxy URIs:
    {
       "socks4://foopy",  // No port.
       "socks4://foopy:1080",
       net::ProxyServer::SCHEME_SOCKS4,
       "foopy",
       1080,
       "SOCKS foopy:1080"
    },
    {
       "socks4://foopy:10",
       "socks4://foopy:10",
       net::ProxyServer::SCHEME_SOCKS4,
       "foopy",
       10,
       "SOCKS foopy:10"
    },

    // SOCKS5 proxy URIs
    {
       "socks5://foopy",  // No port.
       "socks5://foopy:1080",
       net::ProxyServer::SCHEME_SOCKS5,
       "foopy",
       1080,
       "SOCKS5 foopy:1080"
    },
    {
       "socks5://foopy:10",
       "socks5://foopy:10",
       net::ProxyServer::SCHEME_SOCKS5,
       "foopy",
       10,
       "SOCKS5 foopy:10"
    },

    // SOCKS proxy URIs (should default to SOCKS5)
    {
       "socks://foopy",  // No port.
       "socks5://foopy:1080",
       net::ProxyServer::SCHEME_SOCKS5,
       "foopy",
       1080,
       "SOCKS5 foopy:1080"
    },
    {
       "socks://foopy:10",
       "socks5://foopy:10",
       net::ProxyServer::SCHEME_SOCKS5,
       "foopy",
       10,
       "SOCKS5 foopy:10"
    },

    // HTTPS proxy URIs:
    {
       "https://foopy",     // No port
       "https://foopy:443",
       net::ProxyServer::SCHEME_HTTPS,
       "foopy",
       443,
       "HTTPS foopy:443"
    },
    {
       "https://foopy:10",  // Non-standard port
       "https://foopy:10",
       net::ProxyServer::SCHEME_HTTPS,
       "foopy",
       10,
       "HTTPS foopy:10"
    },
    {
       "https://1.2.3.4:10",  // IP Address
       "https://1.2.3.4:10",
       net::ProxyServer::SCHEME_HTTPS,
       "1.2.3.4",
       10,
       "HTTPS 1.2.3.4:10"
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    net::ProxyServer uri =
        net::ProxyServer::FromURI(tests[i].input_uri,
                                  net::ProxyServer::SCHEME_HTTP);
    EXPECT_TRUE(uri.is_valid());
    EXPECT_FALSE(uri.is_direct());
    EXPECT_EQ(tests[i].expected_uri, uri.ToURI());
    EXPECT_EQ(tests[i].expected_scheme, uri.scheme());
    EXPECT_EQ(tests[i].expected_host, uri.host_port_pair().host());
    EXPECT_EQ(tests[i].expected_port, uri.host_port_pair().port());
    EXPECT_EQ(tests[i].expected_pac_string, uri.ToPacString());
  }
}

TEST(ProxyServerTest, DefaultConstructor) {
  net::ProxyServer proxy_server;
  EXPECT_FALSE(proxy_server.is_valid());
}

// Test parsing of the special URI form "direct://". Analagous to the "DIRECT"
// entry in a PAC result.
TEST(ProxyServerTest, Direct) {
  net::ProxyServer uri =
      net::ProxyServer::FromURI("direct://", net::ProxyServer::SCHEME_HTTP);
  EXPECT_TRUE(uri.is_valid());
  EXPECT_TRUE(uri.is_direct());
  EXPECT_EQ("direct://", uri.ToURI());
  EXPECT_EQ("DIRECT", uri.ToPacString());
}

// Test parsing some invalid inputs.
TEST(ProxyServerTest, Invalid) {
  const char* tests[] = {
    "",
    "   ",
    "dddf:",   // not a valid port
    "dddd:d",  // not a valid port
    "http://",  // not a valid host/port.
    "direct://xyz",  // direct is not allowed a host/port.
    "http:/",  // ambiguous, but will fail because of bad port.
    "http:",  // ambiguous, but will fail because of bad port.
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    net::ProxyServer uri =
        net::ProxyServer::FromURI(tests[i], net::ProxyServer::SCHEME_HTTP);
    EXPECT_FALSE(uri.is_valid());
    EXPECT_FALSE(uri.is_direct());
    EXPECT_FALSE(uri.is_http());
    EXPECT_FALSE(uri.is_socks());
  }
}

// Test that LWS (SP | HT) is disregarded from the ends.
TEST(ProxyServerTest, Whitespace) {
  const char* tests[] = {
    "  foopy:80",
    "foopy:80   \t",
    "  \tfoopy:80  ",
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    net::ProxyServer uri =
        net::ProxyServer::FromURI(tests[i], net::ProxyServer::SCHEME_HTTP);
    EXPECT_EQ("foopy:80", uri.ToURI());
  }
}

// Test parsing a ProxyServer from a PAC representation.
TEST(ProxyServerTest, FromPACString) {
  const struct {
    const char* input_pac;
    const char* expected_uri;
  } tests[] = {
    {
       "PROXY foopy:10",
       "foopy:10",
    },
    {
       "   PROXY    foopy:10   ",
       "foopy:10",
    },
    {
       "pRoXy foopy:10",
       "foopy:10",
    },
    {
       "PROXY foopy",  // No port.
       "foopy:80",
    },
    {
       "socks foopy",
       "socks4://foopy:1080",
    },
    {
       "socks4 foopy",
       "socks4://foopy:1080",
    },
    {
       "socks5 foopy",
       "socks5://foopy:1080",
    },
    {
       "socks5 foopy:11",
       "socks5://foopy:11",
    },
    {
       " direct  ",
       "direct://",
    },
    {
       "https foopy",
       "https://foopy:443",
    },
    {
       "https foopy:10",
       "https://foopy:10",
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    net::ProxyServer uri = net::ProxyServer::FromPacString(tests[i].input_pac);
    EXPECT_TRUE(uri.is_valid());
    EXPECT_EQ(tests[i].expected_uri, uri.ToURI());
  }
}

// Test parsing a ProxyServer from an invalid PAC representation.
TEST(ProxyServerTest, FromPACStringInvalid) {
  const char* tests[] = {
    "PROXY",  // missing host/port.
    "HTTPS",  // missing host/port.
    "SOCKS",  // missing host/port.
    "DIRECT foopy:10",  // direct cannot have host/port.
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    net::ProxyServer uri = net::ProxyServer::FromPacString(tests[i]);
    EXPECT_FALSE(uri.is_valid());
  }
}

TEST(ProxyServerTest, ComparatorAndEquality) {
  struct {
    // Inputs.
    const char* server1;
    const char* server2;

    // Expectation.
    //   -1 means server1 is less than server2
    //    0 means server1 equals server2
    //    1 means server1 is greater than server2
    int expected_comparison;
  } tests[] = {
    { // Equal.
      "foo:11",
      "http://foo:11",
      0
    },
    { // Port is different.
      "foo:333",
      "foo:444",
      -1
    },
    { // Host is different.
      "foo:33",
      "bar:33",
      1
    },
    { // Scheme is different.
      "socks4://foo:33",
      "http://foo:33",
      1
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    // Parse the expected inputs to ProxyServer instances.
    const net::ProxyServer server1 =
        net::ProxyServer::FromURI(
            tests[i].server1, net::ProxyServer::SCHEME_HTTP);

    const net::ProxyServer server2 =
        net::ProxyServer::FromURI(
            tests[i].server2, net::ProxyServer::SCHEME_HTTP);

    switch (tests[i].expected_comparison) {
      case -1:
        EXPECT_TRUE(server1 < server2);
        EXPECT_FALSE(server2 < server1);
        EXPECT_FALSE(server2 == server1);
        break;
      case 0:
        EXPECT_FALSE(server1 < server2);
        EXPECT_FALSE(server2 < server1);
        EXPECT_TRUE(server2 == server1);
        break;
      case 1:
        EXPECT_FALSE(server1 < server2);
        EXPECT_TRUE(server2 < server1);
        EXPECT_FALSE(server2 == server1);
        break;
      default:
        FAIL() << "Invalid expectation. Can be only -1, 0, 1";
    }
  }
}