/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

// Unit-test QueryParams.

#include "pagespeed/kernel/http/query_params.h"

#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/http/google_url.h"

namespace {

const char kQueryUrl[] = "http://test.com/?a=1&b&c=2&d=&a=3";
const char kGoodQueryString[] = "x=1&y&z=&x=2";
const char kBadQueryString[] = "a=1\n2&b&c=\"<! ^>\"\r3&d=\t&a=3#4";
const char kSanitizedBadQueryString[] = "a=12&b&c=%22%3C!%20^%3E%223&d=&a=3";

}  // namespace

namespace net_instaweb {

class QueryParamsTest : public testing::Test {
 protected:
  QueryParamsTest() { }

  virtual void SetUp() {
    gurl_.Reset(kQueryUrl);
    query_params_.ParseFromUrl(gurl_);
  }

  GoogleUrl gurl_;
  QueryParams query_params_;

 private:
  DISALLOW_COPY_AND_ASSIGN(QueryParamsTest);
};

TEST_F(QueryParamsTest, TestParseFromUrl) {
  ASSERT_EQ(5, query_params_.size());
  EXPECT_STREQ("a", query_params_.name(0));
  EXPECT_STREQ("1", *(query_params_.EscapedValue(0)));
  EXPECT_STREQ("b", query_params_.name(1));
  EXPECT_EQ(NULL, query_params_.EscapedValue(1));
  EXPECT_STREQ("c", query_params_.name(2));
  EXPECT_STREQ("2", *(query_params_.EscapedValue(2)));
  EXPECT_STREQ("d", query_params_.name(3));
  EXPECT_STREQ("", *(query_params_.EscapedValue(3)));
  EXPECT_STREQ("a", query_params_.name(4));
  EXPECT_STREQ("3", *(query_params_.EscapedValue(4)));
  EXPECT_STREQ(gurl_.Query(), query_params_.ToEscapedString());
}

TEST_F(QueryParamsTest, TestParseFromUntrustedString) {
  // First try a string that doesn't have anything "unsafe".
  QueryParams query_params1;
  query_params1.ParseFromUntrustedString(kGoodQueryString);
  ASSERT_EQ(4, query_params1.size());
  EXPECT_STREQ("x", query_params1.name(0));
  EXPECT_STREQ("1", *query_params1.EscapedValue(0));
  EXPECT_STREQ("y", query_params1.name(1));
  EXPECT_EQ(NULL, query_params1.EscapedValue(1));
  EXPECT_STREQ("z", query_params1.name(2));
  EXPECT_STREQ("", *query_params1.EscapedValue(2));
  EXPECT_STREQ("x", query_params1.name(3));
  EXPECT_STREQ("2", *query_params1.EscapedValue(3));
  EXPECT_STREQ(kGoodQueryString, query_params1.ToEscapedString());
  // Now try a string that has crazy stuff in it.
  QueryParams query_params2;
  query_params2.ParseFromUntrustedString(kBadQueryString);
  ASSERT_EQ(5, query_params2.size());
  EXPECT_STREQ("a", query_params2.name(0));
  EXPECT_STREQ("12", *query_params2.EscapedValue(0));
  EXPECT_STREQ("b", query_params2.name(1));
  EXPECT_EQ(NULL, query_params2.EscapedValue(1));
  EXPECT_STREQ("c", query_params2.name(2));
  EXPECT_STREQ("%22%3C!%20^%3E%223", *query_params2.EscapedValue(2));
  EXPECT_STREQ("d", query_params2.name(3));
  EXPECT_STREQ("", *query_params2.EscapedValue(3));
  EXPECT_STREQ("a", query_params2.name(4));
  EXPECT_STREQ("3", *query_params2.EscapedValue(4));
  EXPECT_STREQ(kSanitizedBadQueryString, query_params2.ToEscapedString());
}

TEST_F(QueryParamsTest, TestLookup) {
  ConstStringStarVector v;
  ASSERT_TRUE(query_params_.LookupEscaped("a", &v));
  ASSERT_EQ(2, v.size());
  EXPECT_STREQ("1", *(v[0]));
  EXPECT_STREQ("3", *(v[1]));
  ASSERT_TRUE(query_params_.LookupEscaped("b", &v));
  ASSERT_EQ(1, v.size());
  EXPECT_EQ(NULL, v[0]);
  ASSERT_TRUE(query_params_.LookupEscaped("c", &v));
  ASSERT_EQ(1, v.size());
  EXPECT_STREQ("2", *(v[0]));
  ASSERT_TRUE(query_params_.LookupEscaped("d", &v));
  ASSERT_EQ(1, v.size());
  EXPECT_STREQ("", *(v[0]));
}

TEST_F(QueryParamsTest, TestRemove) {
  query_params_.RemoveAll("a");
  EXPECT_STREQ("b&c=2&d=", query_params_.ToEscapedString());
  EXPECT_EQ(3, query_params_.size());
  query_params_.RemoveAll("b");
  EXPECT_STREQ("c=2&d=", query_params_.ToEscapedString());
  EXPECT_EQ(2, query_params_.size());
  query_params_.RemoveAll("c");
  EXPECT_STREQ("d=", query_params_.ToEscapedString());
  EXPECT_EQ(1, query_params_.size());
  query_params_.RemoveAll("d");
  EXPECT_STREQ("", query_params_.ToEscapedString());
  EXPECT_EQ(0, query_params_.size());
}

TEST_F(QueryParamsTest, TestClear) {
  query_params_.Clear();
  EXPECT_STREQ("", query_params_.ToEscapedString());
  EXPECT_EQ(0, query_params_.size());
}

TEST_F(QueryParamsTest, TestAEqualsBEquals1) {
  GoogleUrl gurl("http://test.com/?a=b=1");
  ASSERT_TRUE(gurl.IsWebValid());
  query_params_.Clear();
  query_params_.ParseFromUrl(gurl);
  ASSERT_EQ(1, query_params_.size());
  ASSERT_EQ(GoogleString("a"), query_params_.name(0));
  ASSERT_EQ(GoogleString("b=1"), *(query_params_.EscapedValue(0)));
}

TEST_F(QueryParamsTest, EscapedQuery) {
  GoogleUrl gurl("http://example.com/a?b=<value requiring escapes>");
  ASSERT_TRUE(gurl.IsWebValid());

  // Note that GoogleUrl::Escape will turn " " into "+", but the escaper
  // built into the GoogleUrl constructor will turn " " into "%20".
  EXPECT_STREQ("b=%3Cvalue%20requiring%20escapes%3E", gurl.Query());

  query_params_.Clear();
  query_params_.ParseFromUrl(gurl);
  ASSERT_EQ(1, query_params_.size());
  EXPECT_STREQ("b", query_params_.name(0));
  GoogleString unescaped_value;
  ASSERT_TRUE(query_params_.UnescapedValue(0, &unescaped_value));
  EXPECT_STREQ("<value requiring escapes>", unescaped_value);
}

TEST_F(QueryParamsTest, QueryParamWithAndWithoutValues) {
  GoogleUrl gurl("http://example.com/a?b=c&d&e=&f");
  ASSERT_TRUE(gurl.IsWebValid());
  query_params_.Clear();
  query_params_.ParseFromUrl(gurl);
  GoogleString unescaped;

  // A normal query param, b=c, we get back "c".
  EXPECT_STREQ("b", query_params_.name(0));
  EXPECT_STREQ("c", *query_params_.EscapedValue(0));
  EXPECT_TRUE(query_params_.UnescapedValue(0, &unescaped));
  EXPECT_STREQ("c", unescaped);

  // "d" does not have an equals sign.  NULL is returned for the value.
  EXPECT_STREQ("d", query_params_.name(1));
  EXPECT_TRUE(NULL == query_params_.EscapedValue(1));
  EXPECT_FALSE(query_params_.UnescapedValue(1, &unescaped));

  // "e" has an equals sign, but no value before the next "&", so
  // we get back an empty string.
  EXPECT_STREQ("e", query_params_.name(2));
  EXPECT_STREQ("", *query_params_.EscapedValue(2));
  EXPECT_TRUE(query_params_.UnescapedValue(2, &unescaped));
  EXPECT_STREQ("", unescaped);
}

}  // namespace net_instaweb
